Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
Wanneer u werkt met async en await, spelen twee contexttypen belangrijke maar zeer verschillende rollen: ExecutionContext en SynchronizationContext. Je leert wat ieder doet, hoe ieder met async/await interageert, en waarom SynchronizationContext.Current niet over afwachtingspunten stroomt.
Wat is ExecutionContext?
ExecutionContext is een container voor omgevingsstatus die stroomt met de logische controlestroom van uw programma. In een synchrone wereld bevindt omgevingsinformatie zich in TLS (Thread-Local Storage) en alle code die op een bepaalde thread wordt uitgevoerd, ziet die gegevens. In een asynchrone wereld kan een logische bewerking beginnen op één thread, onderbreken en hervatten op een andere thread. Thread-lokale gegevens worden niet automatisch gevolgd—ExecutionContext zorgt ervoor dat ze wel worden gevolgd.
Hoe ExecutionContext stroomt
Vastleggen ExecutionContext met behulp van ExecutionContext.Capture(). Herstel deze tijdens de uitvoering van een gemachtigde met behulp van ExecutionContext.Run:
static void ExecutionContextCaptureDemo()
{
// Capture the current ExecutionContext
ExecutionContext? ec = ExecutionContext.Capture();
// Later, run a delegate within that captured context
if (ec is not null)
{
ExecutionContext.Run(ec, _ =>
{
// Code here sees the ambient state from the point of capture
Console.WriteLine("Running inside captured ExecutionContext.");
}, null);
}
}
Sub ExecutionContextCaptureExample()
' Capture the current ExecutionContext
Dim ec As ExecutionContext = ExecutionContext.Capture()
' Later, run a delegate within that captured context
If ec IsNot Nothing Then
ExecutionContext.Run(ec,
Sub(state)
' Code here sees the ambient state from the point of capture
Console.WriteLine("Running inside captured ExecutionContext.")
End Sub, Nothing)
End If
End Sub
Alle asynchrone API's in .NET die fork werken: Run, QueueUserWorkItem, BeginRead en anderen: leg ExecutionContext vast en gebruik de opgeslagen context bij het aanroepen van uw callback. Dit proces van het vastleggen van de status op de ene thread en het herstellen ervan op een andere is wat 'flowing ExecutionContext' betekent.
Wat is SynchronizationContext?
SynchronizationContext is een abstractie die een doelomgeving vertegenwoordigt waar u wilt werken. Verschillende UI-frameworks bieden hun eigen implementaties:
- Windows Forms biedt
WindowsFormsSynchronizationContextaan, waarmee Post wordt overschreven omControl.BeginInvokeaan te roepen. - WPF biedt
DispatcherSynchronizationContext, waarmee Post wordt overschreven omDispatcher.BeginInvokeaan te roepen. - ASP.NET (op .NET Framework) beschikte over een eigen context die ervoor zorgde dat
HttpContext.Currentbeschikbaar was.
Door in plaats van frameworkspecifieke marshaling-API's te gebruiken SynchronizationContext , kunt u onderdelen schrijven die werken in UI-frameworks:
static class SyncContextExample
{
public static void DoWork()
{
// Capture the current SynchronizationContext
SynchronizationContext? sc = SynchronizationContext.Current;
ThreadPool.QueueUserWorkItem(_ =>
{
// ... do work on the ThreadPool ...
if (sc is not null)
{
sc.Post(_ =>
{
// This runs on the original context (e.g. UI thread)
Console.WriteLine("Back on the original context.");
}, null);
}
});
}
}
Class SyncContextExample
Public Shared Sub DoWork()
' Install a custom SynchronizationContext for demonstration
Dim customContext As New SimpleSynchronizationContext()
SynchronizationContext.SetSynchronizationContext(customContext)
' Capture the current SynchronizationContext
Dim sc As SynchronizationContext = SynchronizationContext.Current
ThreadPool.QueueUserWorkItem(
Sub(state)
' ... do work on the ThreadPool ...
If sc IsNot Nothing Then
sc.Post(
Sub(s)
' This runs on the original context (e.g. UI thread)
Console.WriteLine("Back on the original context.")
End Sub, Nothing)
Else
Console.WriteLine("No SynchronizationContext was captured.")
End If
End Sub)
End Sub
End Class
' A minimal SynchronizationContext for demonstration purposes
Class SimpleSynchronizationContext
Inherits SynchronizationContext
Public Overrides Sub Post(d As SendOrPostCallback, state As Object)
' Queue the callback to run on a thread pool thread
ThreadPool.QueueUserWorkItem(
Sub(s)
d(state)
End Sub)
End Sub
End Class
Een SynchronizationContext vastleggen
Wanneer u een SynchronizationContextopname maakt, leest u de verwijzing uit SynchronizationContext.Current en slaat u deze op voor later gebruik. Vervolgens roept Post u de vastgelegde verwijzing aan om werk terug te plannen naar die omgeving.
Flowing ExecutionContext versus het gebruik van SynchronizationContext
Hoewel beide mechanismen betrekking hebben op het vastleggen van de status van een thread, dienen ze verschillende doeleinden:
- Flowing ExecutionContext betekent het vastleggen van de omgevingsstatus en het actueel maken van diezelfde status tijdens de uitvoering van een gedelegeerde. De gedelegeerde wordt uitgevoerd waar deze wordt uitgevoerd. De status volgt deze.
- Het gebruik van SynchronizationContext betekent het vastleggen van een planningsdoel en het gebruiken ervan om te bepalen waar een gedelegeerde wordt uitgevoerd. De vastgelegde context bepaalt waar de gedelegeerde wordt uitgevoerd.
Kortom: ExecutionContext antwoorden 'welke omgeving moet zichtbaar zijn?' terwijl SynchronizationContext antwoorden 'waar moet de code worden uitgevoerd?'
Hoe async/await communiceert met beide contexten
De async/await infrastructuur communiceert automatisch met beide contexten, maar op verschillende manieren.
ExecutionContext volgt altijd
Wanneer een await een methode opschort (omdat de wachter IsCompletedfalse teruggeeft), legt de infrastructuur een ExecutionContext vast. Wanneer de methode wordt hervat, wordt de vervolgbewerking uitgevoerd binnen de vastgelegde context. Dit gedrag is ingebouwd in de opbouwfuncties voor asynchrone methoden (bijvoorbeeld AsyncTaskMethodBuilder) en is van toepassing, ongeacht het type wachtbaar dat u gebruikt.
SuppressFlow() bestaat, maar het is geen await-specifieke switch zoals ConfigureAwait(false). Het voorkomt ExecutionContext vastleggen voor werk dat u in de wachtrij zet zolang de onderdrukking actief is. Het biedt geen optie perawait programmeermodel waarmee de opbouwfuncties voor asynchrone methoden het herstellen van de vastgelegde ExecutionContext methode voor een vervolg moeten overslaan. Dat ontwerp is opzettelijk omdat ExecutionContext ondersteuning op infrastructuurniveau is die thread-lokale semantiek simuleert in een asynchrone wereld, en de meeste ontwikkelaars hoeven er nooit over na te denken.
Taakwachters leggen SynchronizationContext vast
De wachtfuncties voor Task en Task<TResult> bevatten ondersteuning voor SynchronizationContext. De asynchrone methodebouwers bevatten deze ondersteuning niet.
Wanneer u await een taak uitvoert:
- De wachter controleert SynchronizationContext.Current.
- Als er een context bestaat, legt de wachter deze vast.
- Wanneer de taak is voltooid, wordt de voortzetting teruggezet naar die vastgelegde context in plaats van te worden uitgevoerd op de voltooiingsthread of de threadpool.
await "brengt u terug naar waar u was", dat is hoe dit gedrag werkt. Bijvoorbeeld, hervatten van de UI-thread in een bureaubladtoepassing.
ConfigureAwait bepaalt het vastleggen van de SynchronizationContext
Als u het marshalinggedrag niet wilt, roep dan ConfigureAwait aan met false:
await task.ConfigureAwait(false);
Wanneer u continueOnCapturedContext instelt op false, controleert de wachter niet op een SynchronizationContext en wordt de vervolgbewerking uitgevoerd waar de taak is voltooid (meestal op een threadpool-thread). Auteurs van bibliotheken moeten ConfigureAwait(false) op elke await gebruiken, tenzij de code specifiek hervat moet worden in de vastgelegde context.
SynchronizationContext.Current wordt niet doorgegeven bij awaits
Dit punt is het belangrijkste: SynchronizationContext.Currentloopt niet over await-punten. De asynchrone methodebouwers in de runtime maken gebruik van interne overbelastingen die expliciet onderdrukken SynchronizationContext van stromen als onderdeel van ExecutionContext.
Waarom dit belangrijk is
SynchronizationContext Technisch gezien is dit een van de subcontexten die ExecutionContext kunnen bevatten. Als het als onderdeel van ExecutionContext wordt gestroomd, kan code die op een thread pool thread wordt uitgevoerd een gebruikersinterface SynchronizationContext als Current zien, niet omdat die thread de UI-thread is, maar omdat de context via flow is gelekt. Deze wijziging zou de betekenis van SynchronizationContext.Current 'de omgeving waarin ik momenteel ben' veranderen in 'de omgeving die historisch bestond ergens in de oproepketen'.
Het voorbeeld van Task.Run
Houd rekening met code die werk naar de thread-pool verplaatst. Het hier beschreven UI-threadgedrag is alleen van toepassing wanneer SynchronizationContext.Current niet null is, zoals het geval is in een UI-app.
static class TaskRunExample
{
public static async Task ProcessOnUIThread()
{
// This method is called from a thread with a SynchronizationContext.
// Task.Run offloads work to the thread pool.
string result = await Task.Run(async () =>
{
string data = await DownloadAsync();
// Compute runs on the thread pool, not the original context,
// because SynchronizationContext doesn't flow into Task.Run.
return Compute(data);
});
// Back on the original context (the continuation is posted back).
Console.WriteLine(result);
}
private static async Task<string> DownloadAsync()
{
await Task.Delay(100);
return "downloaded data";
}
private static string Compute(string data) =>
$"Computed: {data.Length} chars";
}
Class TaskRunExampleClass
Public Shared Async Function ProcessOnUIThread() As Task
' If a SynchronizationContext is present when this method starts,
' the outer await captures it. Task.Run still offloads work to the thread pool.
Dim result As String = Await Task.Run(
Async Function()
Dim data As String = Await DownloadAsync()
' Compute runs on the thread pool, not the caller's context,
' because SynchronizationContext doesn't flow into Task.Run.
Return Compute(data)
End Function)
' Resume on the captured context, if one was available.
Console.WriteLine(result)
End Function
Private Shared Async Function DownloadAsync() As Task(Of String)
Await Task.Delay(100)
Return "downloaded data"
End Function
Private Shared Function Compute(data As String) As String
Return $"Computed: {data.Length} chars"
End Function
End Class
In een console-app is SynchronizationContext.Current meestal null, waardoor het codefragment niet wordt hervat op een echte UI-thread. In plaats daarvan illustreert het fragment de regel conceptueel: als een gebruikersinterface SynchronizationContext over await punten stroomt, zou de await binnen de gedelegeerde die aan Task.Run is doorgegeven, die UI-context zien als Current. Het vervolg na await DownloadAsync() wordt vervolgens teruggeplaatst naar de UI-thread, waardoor Compute(data) wordt uitgevoerd op de UI-thread in plaats van op de threadpool. Dat gedrag verslaat het doel van de Task.Run oproep.
Omdat de runtime de SynchronizationContext flow in ExecutionContext onderdrukt, erft de await binnenin Task.Run geen externe UI-context en blijft de voortzetting op de draadpool werken zoals bedoeld.
Overzicht
| Aspect | Uitvoeringscontext | SynchronizationContext |
|---|---|---|
| Purpose | Draagt de omgevingsstatus over asynchrone grenzen | Vertegenwoordigt een doelscheduler (waar de code moet worden uitgevoerd) |
| Vastgelegd door | Opbouwfuncties voor Async-methoden (infrastructuur) | Taakwachters (await task) |
| Stromen in afwachting? | Ja, altijd | Nee, vastgelegd en gepost naar, niet gestroomd |
| Suppression API |
ExecutionContext.SuppressFlow (geavanceerd; zelden nodig) |
ConfigureAwait(false) |
| Scope | Alle wachtende exemplaren |
Task en Task<TResult> (aangepaste async-wachters kunnen vergelijkbare logica toevoegen) |