Wie kann eine asynchrone Methode einen Wert zurückgeben?

75

Ich weiß, wie man Async-Methoden erstellt, sage aber, dass ich eine Methode habe, die viel Arbeit leistet und dann einen booleschen Wert zurückgibt.

Wie gebe ich den booleschen Wert beim Rückruf zurück?

Klarstellung :

public bool Foo(){
    Thread.Sleep(100000); // Do work
    return true;
}

Ich möchte dies asynchron machen können.

Überladung119
quelle

Antworten:

57

Es gibt einige Möglichkeiten, dies zu tun. Am einfachsten ist es, wenn die asynchrone Methode auch die Folgeoperation ausführt. Ein anderer beliebter Ansatz ist die Weitergabe eines Rückrufs, d. H.

void RunFooAsync(..., Action<bool> callback) {
     // do some stuff
     bool result = ...

     if(callback != null) callback(result);
}

Ein anderer Ansatz wäre, ein Ereignis (mit dem Ergebnis in den Ereignisargumentdaten) auszulösen, wenn der asynchrone Vorgang abgeschlossen ist.

Wenn Sie die TPL verwenden, können Sie außerdem Folgendes verwenden ContinueWith:

Task<bool> outerTask = ...;
outerTask.ContinueWith(task =>
{
    bool result = task.Result;
    // do something with that
});
Marc Gravell
quelle
4
Schönes einfaches Beispiel für einen
Rückrufansatz
133

Ab C # 5.0 können Sie die Methode als angeben

public async Task<bool> doAsyncOperation()
{
    // do work
    return true;
}

bool result = await doAsyncOperation();
Dave Arkell
quelle
25
Für alle anderen, die sich interessieren, um die Ergebnisse zu erhalten, die Sie verwenden würdenbool result = await doAsyncOperation();
Gordon Tucker
4
Wann immer Sie das Schlüsselwort "await" verwenden, muss es sich in einer Methode befinden, die mindestens mit "async" markiert wurde. Ich dachte ursprünglich, dass nur die Arbeitermethode auf diese Weise markiert werden musste. Selbst wenn Ihre Aufrufermethode nichts zurückgibt, müssen Sie sie beispielsweise "async void MyCallerMethod" nennen
Mike K
4
@KingOfHypocrites lehnt return await Task.FromResult(true)diese Warnung ab.
Bojan Komazec
2
Dies wird eigentlich nichts asynchrones tun, es sei denn, Sie haben awaitetwas im Methodenkörper. Die Rückkehr Task.FromResult(true)ändert daran nichts. Der Methodenkörper wird bis zum ersten Warten synchron auf dem Thread des Aufrufers ausgeführt.
Drew Noakes
2
Sieht für mich so aus, als ob dies funktioniert. Die Zeile "bool result = ..." muss sich ebenfalls in einer asynchronen Methode befinden, daher denke ich nicht, dass dies die Frage wirklich beantwortet
nuander
4

Der wahrscheinlich einfachste Weg, dies zu tun, besteht darin, einen Delegaten zu erstellen und dann BeginInvoke, gefolgt von einem Warten zu einem späteren Zeitpunkt, und einem EndInvoke.

public bool Foo(){
    Thread.Sleep(100000); // Do work
    return true;
}

public SomeMethod()
{
    var fooCaller = new Func<bool>(Foo);
    // Call the method asynchronously
    var asyncResult = fooCaller.BeginInvoke(null, null);

    // Potentially do other work while the asynchronous method is executing.

    // Finally, wait for result
    asyncResult.AsyncWaitHandle.WaitOne();
    bool fooResult = fooCaller.EndInvoke(asyncResult);

    Console.WriteLine("Foo returned {0}", fooResult);
}
Jim Mischel
quelle
Das AsyncWaitHandle ist ein WaitHandle. Es gibt keine WaitOne () -Methode. Sie müssen es auf das reduzieren, was es enthält, um auf den Abschluss zu warten.
the_drow
1
@the_drow: Vielleicht möchten Sie sich dieses Beispiel ansehen : msdn.microsoft.com/en-us/library/… . Schauen Sie sich auch die Dokumentation für die WaitHandle.WaitOneMethode an: msdn.microsoft.com/en-us/library/58195swd.aspx
Jim Mischel
2
Oder Sie können diesen Code einfach in ein C # -Programm einfügen und testen. Es schien für mich zu funktionieren.
Jim Mischel
3

Verwenden Sie einen BackgroundWorker. Sie erhalten nach Abschluss Rückrufe und können den Fortschritt verfolgen. Sie können den Ergebniswert für die Ereignisargumente auf den resultierenden Wert setzen.

    public void UseBackgroundWorker()
    {
        var worker = new BackgroundWorker();
        worker.DoWork += DoWork;
        worker.RunWorkerCompleted += WorkDone;
        worker.RunWorkerAsync("input");
    }

    public void DoWork(object sender, DoWorkEventArgs e)
    {
        e.Result = e.Argument.Equals("input");
        Thread.Sleep(1000);
    }

    public void WorkDone(object sender, RunWorkerCompletedEventArgs e)
    {
        var result = (bool) e.Result;
    }
NerdFury
quelle
Ein Hintergrundarbeiter ist hier kontraproduktiv. Kennen Sie AutoResetEvent / ManualResetEvent nicht?
the_drow
1
@the_drow Ich bin damit nicht einverstanden. Ein BackgroundWorker und das RunWorkerCompleted-Event sind hier ein perfekter Ansatz
Marc Gravell
@the_drow - nein, ich nicht. Ich werde sehen, was ich darüber lernen kann. Aber wenn Sie erklären könnten, warum Sie dies für kontraproduktiv halten, würde ich gerne verstehen.
NerdFury
Es ist kontraproduktiv, da Sie zu viel für etwas codieren, das diese Art von Code nicht wirklich benötigt. Ein Hintergrundarbeiter ist das, was Sie tun, wenn Sie einen lang laufenden Prozess haben, der an die Benutzeroberfläche berichtet. Eine asynchrone Methode in c # muss keinen Fortschritt melden, sondern nur den Abschluss, da andere möglicherweise auf den Abschluss warten (dafür sind Auto / MenualResetEvent vorgesehen). Das asynchrone Methodenmuster ist eine bekannte Redewendung zum asynchronen Aufrufen einer Methode. Die einzig gültigen Ansätze hier sind das, was ich vorgeschlagen habe oder was @Marc vorgeschlagen hat.
the_drow
@MarcGravell: Nicht alles ist ein Nagel. Es kann funktionieren. Es ist semantisch falsch.
the_drow
1

Vielleicht können Sie versuchen, einen Delegaten zu beginnen, der auf Ihre Methode verweist:



    delegate string SynchOperation(string value);

    class Program
    {
        static void Main(string[] args)
        {
            BeginTheSynchronousOperation(CallbackOperation, "my value");
            Console.ReadLine();
        }

        static void BeginTheSynchronousOperation(AsyncCallback callback, string value)
        {
            SynchOperation op = new SynchOperation(SynchronousOperation);
            op.BeginInvoke(value, callback, op);
        }

        static string SynchronousOperation(string value)
        {
            Thread.Sleep(10000);
            return value;
        }

        static void CallbackOperation(IAsyncResult result)
        {
            // get your delegate
            var ar = result.AsyncState as SynchOperation;
            // end invoke and get value
            var returned = ar.EndInvoke(result);

            Console.WriteLine(returned);
        }
    }

Verwenden Sie dann den Wert in der Methode, die Sie als AsyncCallback gesendet haben, um fortzufahren.

Omer Ben
quelle
-3

Sie sollten das EndXXX Ihrer asynchronen Methode verwenden, um den Wert zurückzugeben. EndXXX sollte warten, bis mit dem WaitHandle des IAsyncResult ein Ergebnis vorliegt, und dann mit dem Wert zurückkehren.

the_drow
quelle