Hallo habr Ich präsentiere Ihnen die Übersetzung des
FAQ- Artikels zu
ConfigureAwait von Stephen Taub.

Async /
await über sieben Jahren zu .NET hinzugefügt. Diese Entscheidung hatte nicht nur erhebliche Auswirkungen auf das .NET-Ökosystem, sondern spiegelt sich auch in vielen anderen Sprachen und Frameworks wider. Derzeit wurden viele Verbesserungen in .NET in Bezug auf zusätzliche Sprachkonstrukte implementiert, die Asynchronität verwenden, APIs mit Asynchronitätsunterstützung wurden implementiert, grundlegende Verbesserungen in der Infrastruktur wurden vorgenommen, dank derer
async /
await wie eine Uhr funktioniert (insbesondere wurden Leistung und Diagnosefunktionen verbessert) in .NET Core).
ConfigureAwait ist ein Aspekt von
async /
await , der weiterhin Fragen
async . Ich hoffe, ich kann viele von ihnen beantworten. Ich werde versuchen, diesen Artikel von Anfang bis Ende lesbar zu machen und ihn gleichzeitig im Stil von Antworten auf häufig gestellte Fragen (FAQ) auszuführen, damit in Zukunft auf ihn verwiesen werden kann.
Um wirklich mit
ConfigureAwait umzugehen, werden wir ein wenig zurückgehen.
Was ist ein SynchronizationContext?
Gemäß der
System.Threading.SynchronizationContext- Dokumentation "Bietet grundlegende Funktionen zum Verteilen des Synchronisationskontexts in verschiedenen Synchronisationsmodellen." Diese Definition ist nicht ganz offensichtlich.
In 99,9% der Fälle wird der
SynchronizationContext einfach als Typ mit einer virtuellen
Post Methode verwendet, die einen Delegaten für die asynchrone Ausführung akzeptiert (der
SynchronizationContext andere virtuelle Mitglieder, die jedoch seltener vorkommen und in diesem Artikel nicht behandelt werden). Die
Post Methode des Basistyps
ruft ThreadPool.QueueUserWorkItem einfach auf, um den angegebenen Delegaten asynchron auszuführen. Abgeleitete Typen überschreiben "
Post damit der Delegat zur richtigen Zeit am richtigen Ort ausgeführt werden kann.
Beispielsweise verfügt Windows Forms über einen von SynchronizationContext abgeleiteten
Typ , der
Post neu definiert, dass er
Control.BeginInvoke . Dies bedeutet, dass jeder Aufruf dieser
Post Methode zu einem späteren Zeitpunkt zu einem Aufruf an den Delegaten in dem Thread führt, der dem entsprechenden Steuerelement zugeordnet ist - dem sogenannten UI-Thread. Das Herzstück von Windows Forms ist die Win32-Nachrichtenverarbeitung. Die Nachrichtenschleife wird in einem UI-Thread ausgeführt, der nur darauf wartet, dass neue Nachrichten verarbeitet werden. Diese Meldungen werden durch Mausbewegungen, Klicken, Tastatureingaben, Systemereignisse ausgelöst, die für die Ausführung durch Stellvertreter verfügbar sind. Wenn Sie also eine
SynchronizationContext Instanz für einen UI-Thread in einer Windows Forms-Anwendung haben, müssen Sie den Stellvertreter an die
Post Methode übergeben, um darin einen Vorgang auszuführen.
Windows Presentation Foundation (WPF) verfügt auch über einen von
SynchronizationContext abgeleiteten
Typ mit einer überschriebenen
Post Methode, die den Delegaten auf ähnliche Weise (mithilfe von
Dispatcher.BeginInvoke ) mit WPF Dispatcher-Steuerelement und nicht mit Windows Forms Control an den UI-Stream „leitet“.
Und Windows RunTime (WinRT) verfügt über einen eigenen von
SynchronizationContext abgeleiteten
Typ , der den Delegaten mithilfe von
CoreDispatcher auch in die UI-
CoreDispatcher .
Dies ist, was hinter der Phrase "Run Delegate in UI-Thread" liegt. Sie können Ihren
SynchronizationContext mit der
Post Methode und einigen Implementierungen implementieren. Beispielsweise muss ich mir keine Gedanken darüber machen, in welchem Thread der Stellvertreter ausgeführt wird, aber ich möchte sicherstellen, dass alle
Post Methode in meinem
SynchronizationContext mit einem begrenzten Grad an Parallelität ausgeführt werden. Sie können einen benutzerdefinierten
SynchronizationContext implementieren:
internal sealed class MaxConcurrencySynchronizationContext : SynchronizationContext { private readonly SemaphoreSlim _semaphore; public MaxConcurrencySynchronizationContext(int maxConcurrencyLevel) => _semaphore = new SemaphoreSlim(maxConcurrencyLevel); public override void Post(SendOrPostCallback d, object state) => _semaphore.WaitAsync().ContinueWith(delegate { try { d(state); } finally { _semaphore.Release(); } }, default, TaskContinuationOptions.None, TaskScheduler.Default); public override void Send(SendOrPostCallback d, object state) { _semaphore.Wait(); try { d(state); } finally { _semaphore.Release(); } } }
Das xUnit-Framework verfügt über eine ähnliche
Implementierung von SynchronizationContext. Hier wird es verwendet, um die Menge an Code zu reduzieren, die mit parallelen Tests verbunden ist.
Die Vorteile sind dieselben wie bei jeder Abstraktion: Es wird eine einzige API bereitgestellt, mit der der Delegierte für die Ausführung in die Warteschlange gestellt werden kann, wie es der Programmierer wünscht, ohne die Implementierungsdetails kennen zu müssen. Angenommen, ich schreibe eine Bibliothek, in der ich etwas arbeiten muss, und stelle dann einen Delegierten in den ursprünglichen Kontext zurück. Dazu muss ich den
SynchronizationContext des Kontexts erfassen. Wenn ich die erforderlichen Schritte abgeschlossen habe, muss ich nur die
Post Methode dieses Kontexts aufrufen und sie zur Ausführung an einen Delegaten übergeben. Ich muss nicht wissen, dass Sie für Windows Forms die
Control übernehmen und
BeginInvoke , für WPF
BeginInvoke von
Dispatcher oder den Kontext und die Warteschlange für xUnit abrufen müssen. Ich muss nur den aktuellen
SynchronizationContext und später verwenden. Zu diesem
SynchronizationContext verfügt der
SynchronizationContext über eine
Current Eigenschaft. Dies kann wie folgt implementiert werden:
public void DoWork(Action worker, Action completion) { SynchronizationContext sc = SynchronizationContext.Current; ThreadPool.QueueUserWorkItem(_ => { try { worker(); } finally { sc.Post(_ => completion(), null); } }); }
Sie können einen speziellen Kontext in der
Current Eigenschaft mit der
SynchronizationContext.SetSynchronizationContext Methode festlegen.
Was ist ein Taskplaner?
SynchronizationContext ist eine gebräuchliche Abstraktion für den "Scheduler". Einige Frameworks verwenden dafür ihre eigenen Abstraktionen, und
System.Threading.Tasks keine Ausnahme. Wenn sich in der
Task System.Threading.Tasks.TaskScheduler befinden, die in die Warteschlange gestellt und ausgeführt werden können, werden sie mit
System.Threading.Tasks.TaskScheduler . Es gibt auch eine virtuelle
Post Methode zum Einreihen eines Delegaten (ein Delegatenaufruf wird mithilfe von Standardmechanismen implementiert).
TaskScheduler stellt eine abstrakte
QueueTask Methode
QueueTask (ein
QueueTask wird mithilfe der
ExecuteTask Methode implementiert).
Der Standard-Scheduler, der
TaskScheduler.Default ist ein Thread-Pool. In
TaskScheduler können auch Methoden zum Festlegen von Zeitpunkt und Ort des
TaskScheduler und überschrieben werden. Zu den Kernbibliotheken gehört beispielsweise der Typ
System.Threading.Tasks.ConcurrentExclusiveSchedulerPair . Eine Instanz dieser Klasse bietet zwei
TaskScheduler Eigenschaften:
ExclusiveScheduler und
ConcurrentScheduler . Im
ConcurrentScheduler geplante Aufgaben können parallel ausgeführt werden, wobei jedoch die vom
ConcurrentExclusiveSchedulerPair beim
MaxConcurrencySynchronizationContext festgelegte Einschränkung berücksichtigt wird (ähnlich wie bei
MaxConcurrencySynchronizationContext ). Es wird keine
ConcurrentScheduler Aufgabe ausgeführt, wenn die Aufgabe in
ExclusiveScheduler und jeweils nur eine exklusive Aufgabe ausgeführt werden darf. Dieses Verhalten ist einer Lese- / Schreibsperre sehr ähnlich.
Wie
SynchronizationContext verfügt
TaskScheduler über eine
Current Eigenschaft, die den aktuellen
TaskScheduler . Im Gegensatz zu
SynchronizationContext fehlt jedoch eine Methode zum Festlegen des aktuellen Schedulers. Stattdessen ist der Scheduler der aktuellen Aufgabe zugeordnet. So zeigt dieses Programm beispielsweise
True , da das in
StartNew verwendete Lambda in der
ExclusiveScheduler Instanz von
ConcurrentExclusiveSchedulerPair wird und
TaskScheduler.Current auf diesem Scheduler installiert
TaskScheduler.Current :
using System; using System.Threading.Tasks; class Program { static void Main() { var cesp = new ConcurrentExclusiveSchedulerPair(); Task.Factory.StartNew(() => { Console.WriteLine(TaskScheduler.Current == cesp.ExclusiveScheduler); }, default, TaskCreationOptions.None, cesp.ExclusiveScheduler).Wait(); } }
Interessanterweise bietet
TaskScheduler eine statische
FromCurrentSynchronizationContext Methode. Die Methode erstellt einen neuen
TaskScheduler und stellt die auszuführenden Tasks mithilfe der
Post Methode in den zurückgegebenen
SynchronizationContext.Current Kontext.
In welcher Beziehung stehen SynchronizationContext und TaskScheduler zum Warten?
Angenommen, Sie müssen eine UI-Anwendung mit einer Schaltfläche schreiben. Durch Drücken der Taste wird der Download von Text von der Website gestartet und auf die Schaltfläche
Content . Der Zugriff auf die Schaltfläche sollte nur über die Benutzeroberfläche des Streams erfolgen, in dem sie sich befindet. Wenn wir Datum und Uhrzeit erfolgreich laden und sie in den
Content der Schaltfläche
Content möchten, müssen wir dies über den Stream tun, der die Kontrolle über sie hat. Wenn diese Bedingung nicht erfüllt ist, erhalten wir eine Ausnahme:
System.InvalidOperationException: ' , .'
Wir können den
SynchronizationContext manuell verwenden, um den
Content im
TaskScheduler , z. B. über
TaskScheduler :
private static readonly HttpClient s_httpClient = new HttpClient(); private void downloadBtn_Click(object sender, RoutedEventArgs e) { s_httpClient.GetStringAsync("http://example.com/currenttime").ContinueWith(downloadTask => { downloadBtn.Content = downloadTask.Result; }, TaskScheduler.FromCurrentSynchronizationContext()); }
Und wir können den
SynchronizationContext direkt verwenden:
private static readonly HttpClient s_httpClient = new HttpClient(); private void downloadBtn_Click(object sender, RoutedEventArgs e) { SynchronizationContext sc = SynchronizationContext.Current; s_httpClient.GetStringAsync("http://example.com/currenttime").ContinueWith(downloadTask => { sc.Post(delegate { downloadBtn.Content = downloadTask.Result; }, null); }); }
Beide Optionen verwenden jedoch explizit einen Rückruf. Stattdessen können wir
async /
await :
private static readonly HttpClient s_httpClient = new HttpClient(); private async void downloadBtn_Click(object sender, RoutedEventArgs e) { string text = await s_httpClient.GetStringAsync("http://example.com/currenttime"); downloadBtn.Content = text; }
All dies „funktioniert einfach“ und konfiguriert den
Content im UI-Thread erfolgreich, da das Warten auf eine Aufgabe bei der oben manuell implementierten Version standardmäßig auf
SynchronizationContext.Current und
TaskScheduler.Current . Wenn Sie in C # etwas „erwarten“, konvertiert der Compiler den Code zum
GetAwaiter (durch Aufrufen von
GetAwaiter ) des „erwarteten“ (in diesem Fall Task) in „Warten“ (
TaskAwaiter ). Das "Warten" ist verantwortlich für das Anhängen eines Rückrufs (oft als "Fortsetzung" bezeichnet), der nach Beendigung des Wartens zum Zustandsautomaten zurückruft. Er implementiert dies mit dem Kontext / Scheduler, den er bei der Callback-Registrierung erfasst hat. Wir werden ein bisschen optimieren und konfigurieren, es ist ungefähr so:
object scheduler = SynchronizationContext.Current; if (scheduler is null && TaskScheduler.Current != TaskScheduler.Default) { scheduler = TaskScheduler.Current; }
Hier wird zunächst geprüft, ob der
SynchronizationContext ist und wenn nicht, ob ein nicht standardmäßiger
TaskScheduler . Wenn einer vorhanden ist, wird der erfasste Scheduler verwendet, wenn der Rückruf für den Anruf bereit ist. Andernfalls wird der Rückruf als Teil des Vorgangs ausgeführt, der die erwartete Aufgabe abschließt.
Was macht ConfigureAwait (false)
Die
ConfigureAwait Methode ist nicht speziell: Sie wird weder vom Compiler noch von der Laufzeit auf bestimmte Weise erkannt. Dies ist eine normale Methode, die eine Struktur zurückgibt (
ConfiguredTaskAwaitable - umschließt die ursprüngliche Aufgabe) und einen Booleschen Wert annimmt. Denken Sie daran, dass
await mit jedem Typ verwendet werden kann, der das richtige Muster implementiert. Wenn ein anderer Typ zurückgegeben wird, bedeutet dies, dass der Compiler Zugriff auf die
GetAwaiter Methode (Teil des Musters) der Instanzen erhält, dies jedoch vom von
ConfigureAwait Typ und nicht direkt von der Task. Auf diese Weise können Sie das Warteverhalten für diesen speziellen Kellner ändern.
Das Warten auf den von
ConfigureAwait(continueOnCapturedContext: false) Typ
ConfigureAwait(continueOnCapturedContext: false) anstatt auf
Task warten, wirkt sich direkt auf die oben beschriebene Implementierung der Kontext- / Scheduler-Erfassung aus. Die Logik wird ungefähr so:
object scheduler = null; if (continueOnCapturedContext) { scheduler = SynchronizationContext.Current; if (scheduler is null && TaskScheduler.Current != TaskScheduler.Default) { scheduler = TaskScheduler.Current; } }
Mit anderen Worten, wenn Sie
false angeben, bedeutet dies, dass der Rückruf nicht vorhanden ist, auch wenn ein aktueller Kontext oder Planer vorhanden ist.
Warum muss ich ConfigureAwait (false) verwenden?
ConfigureAwait(continueOnCapturedContext: false) verwendet, um zu verhindern, dass der Rückruf gezwungen wird, den Quellkontext oder den Scheduler aufzurufen. Dies gibt uns mehrere Vorteile:
Leistungssteigerung. Im Gegensatz zum reinen Anrufen entsteht beim Einreihen eines Rückrufs ein Mehraufwand, da dies zusätzlichen Arbeitsaufwand (und in der Regel zusätzliche Zuordnung) erfordert. Außerdem können wir zur Laufzeit keine Optimierung verwenden (wir können mehr optimieren, wenn wir genau wissen, wie der Rückruf aufgerufen wird, aber wenn er an eine beliebige Implementierung der Abstraktion übergeben wird, sind manchmal Einschränkungen erforderlich). Bei stark belasteten Abschnitten können selbst die zusätzlichen Kosten für die Überprüfung des aktuellen
SynchronizationContext und des aktuellen
TaskScheduler (die beide den Zugriff auf die statischen Flüsse implizieren) den Overhead erheblich erhöhen. Wenn für den Code nach dem
await keine Ausführung im ursprünglichen Kontext mithilfe von
ConfigureAwait(false) erforderlich ist, können alle diese Ausgaben vermieden werden, da er nicht unnötig in die Warteschlange gestellt werden muss, alle verfügbaren Optimierungen verwendet und auch unnötigen Zugriff auf die Stream-Statik vermieden werden kann.
Deadlock-Prävention. Betrachten Sie die Bibliotheksmethode,
await zum Herunterladen von Daten aus dem Netzwerk verwendet wird. Sie rufen diese Methode auf und blockieren sie synchron und warten auf den Abschluss der Aufgabe, z. B. mit
.Wait() oder
.Result oder
.GetAwaiter() .GetResult() .
MaxConcurrencySynchronizationContext , was passiert, wenn der Aufruf erfolgt, wenn der aktuelle
SynchronizationContext die Anzahl der darin enthaltenen Vorgänge explizit mithilfe von
MaxConcurrencySynchronizationContext auf 1
MaxConcurrencySynchronizationContext oder implizit, wenn es sich um einen Kontext mit einem einzelnen Thread handelt, der verwendet werden soll (z. B. ein UI-Thread). Auf diese Weise rufen Sie die Methode in einem einzelnen Thread auf und blockieren sie, bis der Vorgang abgeschlossen ist. Der Download startet über das Netzwerk und wartet auf den Abschluss. Wenn Sie auf eine
Task warten,
Task standardmäßig der aktuelle
SynchronizationContext (und in diesem Fall) erfasst. Wenn der Download vom Netzwerk abgeschlossen ist, wird er in die Warteschlange des
SynchronizationContext Rückrufs gestellt, der den Rest des Vorgangs aufruft. Der einzige Thread, der den Rückruf in der Warteschlange verarbeiten kann, wird derzeit blockiert, während auf den Abschluss des Vorgangs gewartet wird. Dieser Vorgang wird erst abgeschlossen, wenn der Rückruf verarbeitet wurde. Deadlock! Dies kann auch dann auftreten, wenn der Kontext die Parallelität nicht auf 1 beschränkt, die Ressourcen jedoch in gewisser Weise begrenzt sind. Stellen Sie sich dieselbe Situation nur mit einem Wert von 4 für
MaxConcurrencySynchronizationContext . Anstatt die Operation einmal auszuführen, stellen wir 4 Aufrufe an den Kontext in die Warteschlange. Jeder Anruf wird getätigt und in Erwartung seines Abschlusses gesperrt. Alle Ressourcen werden jetzt blockiert und warten auf den Abschluss asynchroner Methoden. Sie können nur dann abgeschlossen werden, wenn ihre Rückrufe in diesem Kontext verarbeitet werden. Er ist jedoch bereits voll besetzt. Schon wieder Deadlock. Wenn die Bibliotheksmethode stattdessen
ConfigureAwait(false) verwendet, wird der Rückruf nicht in den ursprünglichen Kontext eingereiht, wodurch Deadlock-Skripts vermieden werden.
Muss ich ConfigureAwait (true) verwenden?
Nein, es sei denn, Sie müssen explizit angeben, dass Sie
ConfigureAwait(false) nicht verwenden
ConfigureAwait(false) (z. B. um Warnungen zur statischen Analyse usw. auszublenden).
ConfigureAwait(true) keine Bedeutung. Wenn Sie
await task.ConfigureAwait(true) await task und
await task.ConfigureAwait(true) , sind sie funktional identisch. Wenn also
ConfigureAwait(true) im Code vorhanden ist, kann es ohne negative Folgen gelöscht werden.
Die
ConfigureAwait Methode nimmt einen booleschen Wert an, da in einigen Situationen möglicherweise eine Variable übergeben werden muss, um die Konfiguration zu steuern. In 99% der Fälle wird der Wert jedoch auf false (
ConfigureAwait(false) .
Wann sollte ConfigureAwait (false) verwendet werden?
Dies hängt davon ab, ob Sie Code auf Anwendungsebene oder universellen Bibliothekscode implementieren.
Beim Schreiben von Anwendungen ist normalerweise ein gewisses Standardverhalten erforderlich. Wenn das Anwendungsmodell / die Umgebung (z. B. Windows Forms, WPF, ASP.NET Core) einen speziellen
SynchronizationContext , gibt es mit ziemlicher Sicherheit einen guten Grund dafür: Der Code ermöglicht es Ihnen, den Synchronisierungskontext für die ordnungsgemäße Interaktion mit dem Anwendungsmodell / der Umgebung zu berücksichtigen. Wenn Sie beispielsweise einen Ereignishandler in einer Windows Forms-Anwendung, einen Test in xUnit oder Code in einem ASP.NET MVC-Controller schreiben, unabhängig davon, ob das Anwendungsmodell einen
SynchronizationContext , müssen Sie gegebenenfalls
SynchronizationContext . Das heißt, wenn sowohl
ConfigureAwait(true) als auch
await , werden Rückrufe / Fortsetzungen an den ursprünglichen Kontext zurückgesendet - alles läuft wie es sollte. Hier können Sie eine allgemeine Regel formulieren:
Verwenden Sie ConfigureAwait(false) nicht, wenn Sie Code auf Anwendungsebene schreiben . Kehren wir zum Click-Handler zurück:
private static readonly HttpClient s_httpClient = new HttpClient(); private async void downloadBtn_Click(object sender, RoutedEventArgs e) { string text = await s_httpClient.GetStringAsync("http://example.com/currenttime"); downloadBtn.Content = text; }
downloadBtn.Content = text sollte im ursprünglichen Kontext ausgeführt werden. Wenn der Code gegen diese Regel
ConfigureAwait (false) und stattdessen
ConfigureAwait (false) verwendet, wird er im ursprünglichen Kontext nicht verwendet:
private static readonly HttpClient s_httpClient = new HttpClient(); private async void downloadBtn_Click(object sender, RoutedEventArgs e) { string text = await s_httpClient.GetStringAsync("http://example.com/currenttime").ConfigureAwait(false);
Dies führt zu unangemessenem Verhalten. Gleiches gilt für Code in einer klassischen ASP.NET-Anwendung, der von
HttpContext.Current abhängt. Bei Verwendung von
ConfigureAwait(false) nachfolgender Versuch, die
Context.Current Funktion zu verwenden, wahrscheinlich zu Problemen.
Dies ist es, was Universalbibliotheken auszeichnet. Sie sind zum Teil universell, weil sie sich nicht um die Umgebung kümmern, in der sie verwendet werden. Sie können sie von einer Webanwendung, von einer Clientanwendung oder von einem Test aus verwenden - das spielt keine Rolle, da der Bibliothekscode für das Anwendungsmodell, in dem er verwendet werden kann, unabhängig ist. Agnostisch bedeutet auch, dass die Bibliothek nichts unternimmt, um mit dem Anwendungsmodell zu interagieren. Beispielsweise erhält sie keinen Zugriff auf Steuerelemente der Benutzeroberfläche, da die Universalbibliothek nichts über sie weiß. Da der Code nicht in einer bestimmten Umgebung ausgeführt werden muss, können wir vermeiden, dass Fortsetzungen / Rückrufe in den ursprünglichen Kontext zurückgesetzt werden. Dies
ConfigureAwait(false) mithilfe von
ConfigureAwait(false) , wodurch die Leistung verbessert und die Zuverlässigkeit erhöht wird. Dies führt uns zu Folgendem:
Wenn Sie Universalbibliothekscode schreiben, verwenden Sie ConfigureAwait(false) . Aus diesem Grund verwendet jedes (oder fast jedes), das in den .NET Core-Laufzeitbibliotheken erwartet wird, ConfigureAwait (false). Mit wenigen Ausnahmen, die höchstwahrscheinlich Fehler sind, werden sie behoben. ,
PR ConfigureAwait(false) HttpClient .
. , (, , , ) , API, . , , ” " . , , Where LINQ:
public static async IAsyncEnumerable<T> WhereAsync(this IAsyncEnumerable<T> source, Func<T, bool> predicate) .
predicate SynchronizationContext ?
WhereAsync , ,
ConfigureAwait(false) .
:
ConfigureAwait(false) /app-model-agnostic .
ConfigureAwait (false), ?
, , . ,
await . , , . , , , ,
ConfigureAwait(false) , , .
ConfigureAwait (false) , — ?
, . FAQ.
await task.ConfigureAwait(false) , ( ),
ConfigureAwait(false) , - , .
,
await , ,
SynchronizationContext TaskScheduler . ,
CryptoStream .NET , .
awaiter , , . , await -
ConfigureAwait(false) ; , , ,
ConfigureAwait(false) .
Task.Run, ConfigureAwait (false)?
, :
Task.Run(async delegate { await SomethingAsync();
ConfigureAwait(false) SomethingAsync() , ,
Task.Run , ,
SynchronizationContext.Current null . ,
Task.Run TaskScheduler.Default ,
TaskScheduler.Current Default . ,
await ,
ConfigureAwait(false) . , . :
Task.Run(async delegate { SynchronizationContext.SetSynchronizationContext(new SomeCoolSyncCtx()); await SomethingAsync();
SomethingAsync SynchronizationContext.Current SomeCoolSyncCtx .
await , SomethingAsync . , , , , , .
/ . / .
, , , . , ,
ConfigureAwait(false) CA2007 . ,
ConfigureAwait , , . , , - , ,
ConfigureAwait(false) .
SynchronizationContext.SetSynchronizationContext, ConfigureAwait (false)?
Nein. , .
:
Task t; SynchronizationContext old = SynchronizationContext.Current; SynchronizationContext.SetSynchronizationContext(null); try { t = CallCodeThatUsesAwaitAsync();
,
CallCodeThatUsesAwaitAsync null . . ,
await TaskScheduler.Current .
TaskScheduler ,
await '
CallCodeThatUsesAwaitAsync TaskScheduler .
Task.Run FAQ, : ,
try , ( ).
:
SynchronizationContext old = SynchronizationContext.Current; SynchronizationContext.SetSynchronizationContext(null); try { await t; } finally { SynchronizationContext.SetSynchronizationContext(old); }
? , . , / . ,
SynchronizationContext , , . , , , , . , , . , . Usw. .
ConfigureAwait(false) GetAwaiter ().GetResult ()?
Nein.
ConfigureAwait . ,
awaiter ,
awaiter '
IsCompleted ,
GetResult OnCompleted ( UnsafeOnCompleted).
ConfigureAwait {Unsafe}OnCompleted ,
GetResult() ,
TaskAwaiter ConfiguredTaskAwaitable.ConfiguredTaskAwaiter .
task.ConfigureAwait(false).GetAwaiter().GetResult() task.GetAwaiter().GetResult() ( , ).
, , SynchronizationContext TaskScheduler. ConfigureAwait(false)?
Möglicherweise. , «». , , , ,
SynchronizationContext TaskScheduler , , . , , .
, .NET Core ConfigureAwait (false). Ist es so?
. .NET Core , .NET Framework. .
,
SynchronizationContext . , ASP.NET .NET Framework
SynchronizationContext , ASP.NET Core . , , ASP.NET Core
SynchronizationContext ,
ConfigureAwait(false) .
,
SynchronizationContext TaskScheduler . - ( , ) , ,
await ' ASP.NET Core ,
ConfigureAwait(false) . , , ( -) ,
ConfigureAwait(false) .
ConfigureAwait, « foreach» IAsyncEnumerable?
Ja .
MSDN .
Await foreach , ,
IAsyncEnumerable<T> . , API. .NET
ConfigureAwait IAsyncEnumerable<T> , ,
IAsyncEnumerable<T> Boolean .
MoveNextAsync DisposeAsync . , , .
ConfigureAwait, 'await using' IAsyncDisposable?
, .
IAsyncEnumerable<T> , .NET
ConfigureAwait IAsyncDisposable await using , , ( ,
DisposeAsync ):
await using (var c = new MyAsyncDisposableClass().ConfigureAwait(false)) { ... }
,
c —
MyAsyncDisposableClass ,
System.Runtime.CompilerServices.ConfiguredAsyncDisposable ,
ConfigureAwait IAsyncDisposable .
, :
var c = new MyAsyncDisposableClass(); await using (c.ConfigureAwait(false)) { ... }
c MyAsyncDisposableClass .
c ; , .
ConfigureAwait (false), AsyncLocal . ?
, .
AsyncLocal<T> ExecutionContext ,
SynchronizationContext .
ExecutionContext ExecutionContext.SuppressFlow() ,
ExecutionContext (, ,
AsyncLocal <T> )
awaits , ,
ConfigureAwait SynchronizationContext .
.
ConfigureAwait(false) ?
ConfigureAwait(false) .
, , // . , , :
1 ,
2 ,
3 ,
4 .
, ,
.