Folgendes meine ich:
public Task<SomeObject> GetSomeObjectByTokenAsync(int id)
{
string token = repository.GetTokenById(id);
if (string.IsNullOrEmpty(token))
{
return Task.FromResult(new SomeObject()
{
IsAuthorized = false
});
}
else
{
return repository.GetSomeObjectByTokenAsync(token).ContinueWith(t =>
{
t.Result.IsAuthorized = true;
return t.Result;
});
}
}
Die obige Methode kann abgewartet werden, und ich denke, sie ähnelt stark dem, was das T- Ask-basierte A- Synchron- P- Muster vorschlägt. (Die anderen mir bekannten Muster sind die APM- und EAP- Muster.)
Was ist nun mit dem folgenden Code:
public async Task<SomeObject> GetSomeObjectByToken(int id)
{
string token = repository.GetTokenById(id);
if (string.IsNullOrEmpty(token))
{
return new SomeObject()
{
IsAuthorized = false
};
}
else
{
SomeObject result = await repository.GetSomeObjectByTokenAsync(token);
result.IsAuthorized = true;
return result;
}
}
Die Hauptunterschiede hierbei sind, dass die Methode async
die await
Schlüsselwörter verwendet und verwendet. Was ändert sich also im Gegensatz zur zuvor geschriebenen Methode? Ich weiß, dass es auch erwartet werden kann. Jede Methode, die Task zurückgibt, kann dies tun, es sei denn, ich irre mich.
Ich bin mir der Zustandsmaschine bewusst, die mit diesen switch-Anweisungen erstellt wurde, wenn eine Methode als gekennzeichnet ist async
, und ich bin mir bewusst, dass sie await
selbst keinen Thread verwendet - sie blockiert überhaupt nicht, der Thread erledigt einfach andere Dinge, bis dies der Fall ist zurückgerufen, um die Ausführung des obigen Codes fortzusetzen.
Aber was ist der zugrunde liegende Unterschied zwischen den beiden Methoden, wenn wir sie mit dem await
Schlüsselwort aufrufen ? Gibt es überhaupt einen Unterschied, und wenn ja, welcher wird bevorzugt?
BEARBEITEN: Ich bin der Meinung, dass das erste Code-Snippet bevorzugt wird, da wir die Schlüsselwörter async / await effektiv eliminieren , ohne dass dies Auswirkungen hat. Wir geben eine Aufgabe zurück, die ihre Ausführung synchron fortsetzt, oder eine bereits abgeschlossene Aufgabe auf dem Hot Path (was sein kann) zwischengespeichert).
result.IsAuthorized = true
wird es im Thread-Pool ausgeführt, während es im zweiten Beispiel möglicherweise im selben Thread ausgeführt wird, der aufgerufen wurdeGetSomeObjectByToken
(wenn ein Thread daraufSynchronizationContext
installiert war, z. B. ein UI-Thread). Das Verhalten beimGetSomeObjectByTokenAsync
Auslösen einer Ausnahme unterscheidet sich ebenfalls geringfügig. Im Allgemeinenawait
wird es vorgezogenContinueWith
, da es fast immer besser lesbar ist.Antworten:
Mit dem Mechanismus
async
/await
wandelt der Compiler Ihren Code in eine Zustandsmaschine um. Ihr Code wird synchron ausgeführt, bis der erste Codeawait
auf einen Wartenden trifft, der gegebenenfalls noch nicht abgeschlossen ist.Im Microsoft C # -Compiler handelt es sich bei dieser Zustandsmaschine um einen
await
Werttyp. Dies bedeutet, dass sie sehr geringe Kosten verursacht, wenn alle zu erwartenden Aufgaben abgeschlossen sind, da sie kein Objekt zuweist und daher keinen Müll generiert. Wenn eine Wartezeit nicht abgeschlossen ist, wird dieser Werttyp unweigerlich eingerahmt.Beachten Sie, dass dies die Zuweisung von
Task
s nicht vermeidet, wenn dies die Art der in denawait
Ausdrücken verwendeten Wartezeiten ist .Mit
ContinueWith
vermeiden Sie Zuordnungen (außerTask
) nur, wenn Ihre Fortsetzung keinen Abschluss hat und wenn Sie entweder kein Statusobjekt verwenden oder ein Statusobjekt so oft wie möglich wiederverwenden (z. B. aus einem Pool).Außerdem wird die Fortsetzung aufgerufen, wenn die Aufgabe abgeschlossen ist und ein Stapelrahmen erstellt wird. Sie wird nicht inline. Das Framework versucht, Stapelüberläufe zu vermeiden, aber es kann vorkommen, dass ein Stapelüberlauf nicht vermieden wird, z. B. wenn große Arrays stapelweise zugewiesen werden.
Dies wird vermieden, indem überprüft wird, wie viel Stapel noch vorhanden ist. Wenn der Stapel durch eine interne Maßnahme als voll angesehen wird, wird die Fortsetzung im Taskplaner geplant. Es wird versucht, schwerwiegende Ausnahmen für Stapelüberläufe auf Kosten der Leistung zu vermeiden.
Hier ist ein subtiler Unterschied zwischen
async
/await
undContinueWith
:async
Ichawait
werde FortsetzungenSynchronizationContext.Current
einplanen, falls vorhanden, andernfalls inTaskScheduler.Current
1ContinueWith
plant Fortsetzungen im bereitgestellten Taskplaner oderTaskScheduler.Current
in den Überladungen ohne den Parameter TaskplanerSo simulieren Sie das Standardverhalten von
async
/await
:Zur Simulation
async
/await
‚s VerhaltenTask
‘ s.ConfigureAwait(false)
:Mit Schleifen und Ausnahmebehandlung wird es immer komplizierter. Abgesehen davon, dass Ihr Code lesbar bleibt , funktioniert
async
/await
mit allen erwarteten .Ihr Fall wird am besten mit einem gemischten Ansatz behandelt: einer synchronen Methode, die bei Bedarf eine asynchrone Methode aufruft. Ein Beispiel für Ihren Code mit diesem Ansatz:
Nach meiner Erfahrung habe ich nur sehr wenige Stellen im Anwendungscode gefunden , an denen sich das Hinzufügen einer solchen Komplexität tatsächlich auszahlt, um solche Ansätze zu entwickeln, zu überprüfen und zu testen, während im Bibliothekscode jede Methode ein Engpass sein kann.
Der einzige Fall, in dem ich dazu neige, Aufgaben zu erledigen, ist, wenn eine
Task
oder eineTask<T>
zurückgegebene Methode einfach das Ergebnis einer anderen asynchronen Methode zurückgibt, ohne selbst eine E / A oder eine Nachbearbeitung durchgeführt zu haben.YMMV.
ConfigureAwait(false)
oder warten auf eine Wartezeit, die benutzerdefinierte Zeitplanung verwendetquelle
private sealed class <Main>d__0 : IAsyncStateMachine
. Die Instanzen dieser Klasse können jedoch wiederverwendbar sein.ContinueWith
?.ContinueWith
sollte programmgesteuert verwendet werden. Besonders dann, wenn Sie CPU-intensive Aufgaben anstelle von E / A-Aufgaben erledigen. Wenn Sie sich jedoch mit denTask
Eigenschaften, derAggregateException
Komplexität und den Komplexitätsbedingungen, Zyklen und Ausnahmebehandlungen befassen müssen, wenn weitere asynchrone Methoden aufgerufen werden, gibt es kaum einen Grund, sich daran zu halten.Task.FromResult("...")
. Im asynchronen Code, wenn Sie Werte von Schlüssel - Cache , die I / O benötigen zu erhalten, können Sie ein Wörterbuch verwenden , bei denen die Werte Aufgaben, zBConcurrentDictionary<string, Task<T>>
stattConcurrentDictionary<string, T>
, VerwendungGetOrAdd
Anruf mit einer Fabrik Funktion und warten auf das Ergebnis. Dies garantiert, dass nur eine E / A-Anforderung zum Auffüllen des Cache-Schlüssels gestellt wird. Sie weckt die Wartenden, sobald die Aufgabe abgeschlossen ist, und dient anschließend als abgeschlossene Aufgabe.Durch die Verwendung verwenden
ContinueWith
Sie die Tools, die vor der Einführung derasync
/await
-Funktionalität mit C # 5 im Jahr 2012 verfügbar waren . Als Tool ist es ausführlich, nicht einfach zusammenzusetzen und erfordert zusätzliche Arbeit zum Auspacken vonAggregateException
s undTask<Task<TResult>>
Rückgabewerten (diese erhalten Sie, wenn Sie übergeben asynchrone Delegaten als Argumente. Im Gegenzug bietet es nur wenige Vorteile. Sie können es verwenden, wenn Sie mehrere Fortsetzungen an dieselbe anhängen möchtenTask
oder in einigen seltenen Fällen, in denen Sieasync
/await
aus irgendeinem Grund nicht verwenden können (z. B. wenn Sie sich in einer Methode mitout
Parametern befinden ).Update: Ich habe den irreführenden Rat entfernt, den
ContinueWith
das verwenden sollteTaskScheduler.Default
, um das Standardverhalten von nachzuahmenawait
. Eigentlichawait
plant der standardmäßig seine Fortsetzung mitTaskScheduler.Current
.quelle