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