ConfigureAwait: Häufig gestellte Fragen

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

Bild

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); //  downloadBtn.Content = text; } 

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(); //    SomeCoolSyncCtx }); 

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(); // await'      } finally { SynchronizationContext.SetSynchronizationContext(old); } await t; //  -     


, 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)) { ... } 

, cMyAsyncDisposableClass , 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 .

, , .

Source: https://habr.com/ru/post/de482354/


All Articles