Ich schaue mir endlich die asynchronen und wartenden Schlüsselwörter an, die ich irgendwie "bekomme", aber alle Beispiele, die ich gesehen habe, rufen asynchrone Methoden im .Net-Framework auf, z. B. dieses , das aufruft HttpClient.GetStringAsync()
.
Was mir nicht so klar ist, ist, was in einer solchen Methode vor sich geht und wie ich meine eigene "erwartete" Methode schreiben würde. Ist es so einfach, den Code, den ich asynchron in einer Task ausführen möchte, zu verpacken und zurückzugeben?
c#
async-await
Andrew Stephens
quelle
quelle
Task
oder zurückgibtTask<T>
. Async ist auf diese Weise gut zusammensetzbar.Antworten:
Es ist so einfach wie
Um es zu einer erwarteten Methode zu machen:
public Task ExpensiveTaskAsync() { return Task.Run(() => ExpensiveTask()); }
Das Wichtigste dabei ist, eine Aufgabe zurückzugeben. Die Methode muss nicht einmal als asynchron markiert werden. (Lesen Sie einfach etwas weiter, damit es ins Bild kommt.)
Dies kann nun als bezeichnet werden
async public void DoStuff() { PrepareExpensiveTask(); await ExpensiveTaskAsync(); UseResultsOfExpensiveTask(); }
Beachten Sie, dass hier die Methodensignatur lautet
async
, da die Methode die Kontrolle bis zur Rückkehr an den AufruferExpensiveTaskAsync()
zurückgeben kann. Auch teuer in diesem Fall bedeutet zeitaufwändig, wie eine Webanforderung oder ähnliches. Um umfangreiche Berechnungen an einen anderen Thread zu senden, ist es normalerweise besser, die "alten" Ansätze zu verwenden, dhSystem.ComponentModel.BackgroundWorker
für GUI-Anwendungen oderSystem.Threading.Thread
.quelle
Task
oder zurückgibtTask<>
. Technisch gesehen können Sie aber auch eine Methode schreiben, die zurückgibt,YourOwnType
sofernYourOwnType
eine öffentliche, parameterlose, nicht statische Instanzmethode mit dem Namen "GetAwaiter()
return return type" vorhanden ist (Details finden Sie an anderer Stelle). Soawait
ist es ein bisschen wieforeach
, es funktioniert auf jedem Typ, der eine geeignete öffentliche Methode hat.foreach
benötigtIEnumerable
. Ich bin ein wenig enttäuscht darüber, dassawait
Enten-Typisierung verwendet wird, wenn eine Benutzeroberfläche für die Sprache besser geeignet wäre. Die Tatsache, dass es "für Compiler" ist, ist eine schlechte Entschuldigung.foreach
benötigt keine Schnittstellen! Probieren Sie einfach diese Klassen aus:class ForeachMe { public StrangeType GetEnumerator() { return new StrangeType(); } } class StrangeType { public bool MoveNext() { return true; } public DateTime Current { get { return DateTime.Now; } } }
Mit ihnenforeach (var x in new ForeachMe()) { Console.WriteLine(x); }
funktioniert der Code einwandfrei.IEnumerable
oderIEnumerable<>
.Dies ist eine Option, aber höchstwahrscheinlich nicht das, was Sie tun möchten, da Sie dadurch nicht viele der Vorteile von asynchronem Code erhalten. Weitere Informationen finden Sie unter Stephen Toubs Soll ich asynchrone Wrapper für synchrone Methoden verfügbar machen?
Im Allgemeinen sind Methoden nicht zu erwarten, Typen sind es. Wenn Sie in der Lage sein wollen , wie etwas zu schreiben
await MyMethod()
, dannMyMethod()
muss zurückkehrenTask
,Task<T>
oder eine benutzerdefinierteawait
Lage Art. Die Verwendung eines benutzerdefinierten Typs ist ein seltenes und fortgeschrittenes Szenario. MitTask
haben Sie mehrere Möglichkeiten:async
undawait
. Dies ist nützlich für Komponieren Aktionen asynchron, aber es kann nicht für den inner meisten verwendet werdenawait
können Anrufe.Task
mit einer der Methoden aufTask
, wieTask.Run()
oderTask.FromAsync()
.TaskCompletionSource
. Dies ist der allgemeinste Ansatz. Er kann verwendet werden, umawait
aus allem, was in Zukunft passieren wird, fähige Methoden zu erstellen .quelle
Die Rückgabe von a
Task
ist nicht der einzige Weg. Sie haben die Möglichkeit, einen benutzerdefinierten Kellner zu erstellen (indem SieGetAwaiter
und implementierenINotifyCompletion
). Hier finden Sie eine gute Lektüre: "Warten Sie auf alles" . Beispiele für .NET-APIs, die benutzerdefinierte Wartende zurückgeben:Task.Yield()
,Dispatcher.InvokeAsync
.Ich habe hier und hier einige Posts mit benutzerdefinierten Wartern , z.
// don't use this in production public static class SwitchContext { public static Awaiter Yield() { return new Awaiter(); } public struct Awaiter : System.Runtime.CompilerServices.INotifyCompletion { public Awaiter GetAwaiter() { return this; } public bool IsCompleted { get { return false; } } public void OnCompleted(Action continuation) { ThreadPool.QueueUserWorkItem((state) => ((Action)state)(), continuation); } public void GetResult() { } } } // ... await SwitchContext.Yield();
quelle
// don't use this in production
- warum genau?await
s, die infinally
s nicht verfügbar sind, und die Konsequenzen, die sowieso nicht mehr gelten. Der Rest ist alles sehr spekulativ, wie schlecht aussehender Code (Task::StartNew
jeder Abschnitt stattawait YieldTo
? Nur wenn Sie es nicht selbst versucht haben) oder semantisch unklar (im Gegensatz zuConfigureAwait(false)
, nehme ich an?).Ja, technisch gesehen müssen Sie nur eine
Task
oderTask<Result>
von einerasync
Methode zurückgeben, um eine erwartete Methode zu implementieren.Dies unterstützt das aufgabenbasierte asynchrone Muster .
Es gibt jedoch verschiedene Möglichkeiten, das TAP zu implementieren. Weitere Informationen finden Sie unter Implementieren des aufgabenbasierten asynchronen Musters .
(Aber all diese Implementierungen kehren immer noch zurück
Task
oderTask<Result>
natürlich.)quelle
Konvertieren Sie einfach Ihre Methode in Task. Wie bei @Romiox verwende ich normalerweise diese Erweiterung:
public static partial class Ext { #region Public Methods public static Task ToTask(Action action) { return Task.Run(action); } public static Task<T> ToTask<T>(Func<T> function) { return Task.Run(function); } public static async Task ToTaskAsync(Action action) { return await Task.Run(action); } public static async Task<T> ToTaskAsync<T>(Func<T> function) { return await Task.Run(function); } #endregion Public Methods }
Lassen Sie uns jetzt sagen, dass Sie haben
void foo1()... void foo2(int i1)... int foo3()... int foo4(int i1)...
...
Dann können Sie Ihre [asynchrone Methode] wie @Romiox deklarieren
async Task foo1Async() { return await Ext.ToTask(() => foo1()); } async Task foo2Async(int i1) { return await Ext.ToTask(() => foo2(i1)); } async Task<int> foo3Async() { return await Ext.ToTask(() => foo3()); } async Task<int> foo4Async(int i1) { return await Ext.ToTask(() => foo4(i1)); }
ODER
async Task foo1Async() { return await Ext.ToTaskAsync(() => foo1()); } async Task foo2Async(int i1) { return await Ext.ToTaskAsync(() => foo2(i1)); } async Task<int> foo3Async() { return await Ext.ToTaskAsync(() => foo3()); } async Task<int> foo4Async(int i1) { return await Ext.ToTaskAsync(() => foo4(i1)); }
...
Jetzt können Sie async verwenden und auf eine der fooAsync-Methoden warten, z. B. foo4Async
async Task<int> TestAsync () { ///Initial Code int m = 3; ///Call the task var X = foo4Async(m); ///Between ///Do something while waiting comes here ///.. var Result = await X; ///Final ///Some Code here return Result; }
quelle
Wenn Sie a nicht verwenden möchten
Task
, können Sie ein vollständig angepasstes, zu erwartendes Objekt schreiben. Ein solches Objekt implementiert eine Methode,GetAwaiter ()
die ein implementiertes Objekt zurückgibtINotifyCompletion
, das das Objekt selbst sein kann.Mehr: INotifyCompletion
Der Kellner implementiert:
IsCompleted
ist den Staat zu bekommenGetResult ()
um das Ergebnis zu erhaltenOnCompleted (Action continuation)
um den Fortsetzungsdelegierten festzulegen.Das erwartete Objekt enthält eine Methode für die tatsächliche Nutzlast (z. B. unten ist die Methode
Run
).class Program { // Need to change the declaration of Main() in order to use 'await' static async Task Main () { // Create a custom awaitable object MyAwaitable awaitable = new MyAwaitable (); // Run awaitable payload, ignore returned Task _ = awaitable.Run (); // Do some other tasks while awaitable is running Console.WriteLine ("Waiting for completion..."); // Wait for completion await awaitable; Console.WriteLine ("The long operation is now complete. " + awaitable.GetResult()); } } public class MyAwaitable : INotifyCompletion { // Fields private Action continuation = null; private string result = string.Empty; // Make this class awaitable public MyAwaitable GetAwaiter () { return this; } // Implementation of INotifyCompletion for the self-awaiter public bool IsCompleted { get; set; } public string GetResult () { return result; } public void OnCompleted (Action continuation) { // Store continuation delegate this.continuation = continuation; Console.WriteLine ("Continuation set"); } // Payload to run public async Task Run () { Console.WriteLine ("Computing result..."); // Wait 2 seconds await Task.Delay (2000); result = "The result is 10"; // Set completed IsCompleted = true; Console.WriteLine ("Result available"); // Continue with the continuation provided continuation?.Invoke (); } }
quelle