Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Ketika Anda bekerja dengan async dan await, dua jenis konteks memainkan peran penting tetapi sangat berbeda: ExecutionContext dan SynchronizationContext. Anda mempelajari apa yang dilakukan masing-masing, bagaimana masing-masing berinteraksi dengan async/await, dan mengapa SynchronizationContext.Current tidak mengalir di titik tunggu.
Apa itu ExecutionContext?
ExecutionContext adalah kontainer untuk keadaan ambien yang mengikuti alur kontrol logis program Anda. Dalam dunia yang sinkron, informasi sekitar berada di penyimpanan lokal utas (TLS), dan semua kode yang berjalan pada utas tertentu melihat data tersebut. Di dunia asinkron, operasi logis dapat dimulai pada satu utas, menangguhkan, dan melanjutkan pada utas yang berbeda. Data thread-local tidak mengikuti secara otomatis—ExecutionContext membuatnya mengikuti.
Cara kerja aliran ExecutionContext
Tangkap ExecutionContext dengan menggunakan ExecutionContext.Capture(). Kembalikan selama eksekusi delegate dengan menggunakan 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
Semua API asinkron dalam .NET yang mencabangkan pekerjaan—Run, QueueUserWorkItem, BeginRead, dan lainnya—menangkap ExecutionContext dan menggunakan konteks tersimpan saat memanggil callback Anda. Proses menangkap status pada satu utas dan memulihkannya di utas lain inilah yang berarti "flowing ExecutionContext".
Apa itu SynchronizationContext?
SynchronizationContext adalah abstraksi yang mewakili lingkungan target tempat Anda ingin bekerja. Kerangka kerja UI yang berbeda menyediakan implementasinya sendiri:
- Formulir Windows menyediakan
WindowsFormsSynchronizationContext, yang menggantikan Post untuk memanggilControl.BeginInvoke. - WPF menyediakan
DispatcherSynchronizationContext, yang mengambil alih Post untuk memanggilDispatcher.BeginInvoke. - ASP.NET (pada .NET Framework) menyediakan konteksnya sendiri yang memastikan
HttpContext.Currenttersedia.
Dengan menggunakan SynchronizationContext alih-alih API marshaling khusus kerangka kerja, Anda dapat menulis komponen yang berfungsi di berbagai kerangka kerja UI.
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
Menangkap SynchronizationContext
Saat Anda mengambil SynchronizationContext, Anda membaca referensi dari SynchronizationContext.Current dan menyimpannya untuk digunakan nanti. Anda kemudian memanggil referensi Post yang telah diambil untuk menjadwalkan pekerjaan kembali ke lingkungan tersebut.
Pengaliran ExecutionContext vs. menggunakan SynchronizationContext
Meskipun kedua mekanisme melibatkan pengambilan status dari utas, kedua mekanisme tersebut melayani tujuan yang berbeda:
- Flowing ExecutionContext berarti menangkap keadaan lingkungan dan menerapkan keadaan yang sama selama eksekusi delegat. Delegasi berjalan di mana pun akhirnya berjalan—status mengikutinya.
- Menggunakan SynchronizationContext berarti menangkap target penjadwalan dan menggunakannya untuk memutuskan lokasi eksekusi delegasi. Konteks yang diambil mengontrol tempat delegasi berjalan.
Singkatnya: ExecutionContext menjawab "lingkungan apa yang harus terlihat?" sambil SynchronizationContext menjawab "di mana kode harus dijalankan?"
Cara asinkron/menunggu berinteraksi dengan kedua konteks
Infrastruktur async/await berinteraksi dengan kedua konteks secara otomatis, tetapi dengan cara yang berbeda.
ExecutionContext selalu mengalir
Setiap kali sebuah await menangguhkan metode (karena properti awaiter IsCompleted mengembalikan false), infrastruktur menangkap sebuah ExecutionContext. Ketika metode dilanjutkan, kelanjutan berjalan dalam konteks yang ditangkap. Perilaku ini sudah terintegrasi dalam penyusun metode asinkron (misalnya, AsyncTaskMethodBuilder) dan berlaku terlepas dari jenis awaitable apa pun yang Anda gunakan.
SuppressFlow() ada, tetapi bukan pengubah khusus 'await' seperti ConfigureAwait(false). Ini menghentikan ExecutionContext pengambilan untuk pekerjaan yang Anda mengantre saat supresi aktif. Ini tidak menyediakan opsi untuk setiap modelawait pemrograman yang menginstruksikan pembangun metode asinkron untuk melewati memulihkan ExecutionContext yang telah diambil untuk kelanjutan. Desain itu disengaja karena ExecutionContext merupakan dukungan tingkat infrastruktur yang mensimulasikan semantik utas lokal dalam lingkungan asinkron, dan sebagian besar pengembang tidak perlu memikirkannya.
Penanti tugas menangkap SynchronizationContext
Penanti dari Task dan Task<TResult> juga mendukung SynchronizationContext. Penyusun metode asinkron tidak menyertakan dukungan ini.
Saat Anda await melakukan tugas:
- Penunggu memeriksa SynchronizationContext.Current.
- Jika terdapat konteks, pengawas menanganinya.
- Ketika tugas selesai, kelanjutan diposting kembali ke konteks yang diambil alih-alih berjalan pada utas yang selesai atau kumpulan utas.
Perilaku ini adalah bagaimana await "membawa Anda kembali ke tempat Anda berada". Misalnya, memulai kembali utas UI di aplikasi desktop.
Kontrol ConfigureAwait menangkap SynchronizationContext
Jika Anda tidak ingin perilaku pemetaan, panggil ConfigureAwait dengan false:
await task.ConfigureAwait(false);
Saat Anda mengatur continueOnCapturedContext ke false, awaiter tidak memeriksa SynchronizationContext dan kelanjutan dieksekusi di mana pun tugas selesai (biasanya pada pool thread). Penulis pustaka harus menggunakan ConfigureAwait(false) pada setiap menunggu kecuali kode secara khusus perlu dilanjutkan pada konteks yang diambil.
SynchronizationContext.Current tidak diteruskan melalui await
Titik ini adalah yang paling penting: SynchronizationContext.Currenttidak bergerak pada titik penundaan. Pembangun metode async dalam runtime menggunakan overload internal yang secara eksplisit menekan SynchronizationContext agar tidak mengalir sebagai bagian dari ExecutionContext.
Mengapa ini penting
Secara teknis, SynchronizationContext adalah salah satu sub-konteks yang ExecutionContext dapat berisi. Jika mengalir sebagai bagian dari ExecutionContext, kode yang dijalankan pada utas kumpulan mungkin melihat UI SynchronizationContext sebagai Current, bukan karena utas tersebut adalah utas UI, tetapi karena konteks "bocor" melalui alur. Perubahan itu akan mengubah arti SynchronizationContext.Current dari "lingkungan yang saat ini saya gunakan" menjadi "lingkungan yang secara historis ada di suatu tempat dalam rantai panggilan."
Contoh Task.Run
Pertimbangkan kode yang memindahkan pekerjaan ke kumpulan utas. Perilaku UI-thread yang dijelaskan di sini hanya berlaku saat SynchronizationContext.Current non-null, seperti di aplikasi UI:
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
Di aplikasi konsol, SynchronizationContext.Current biasanya null, sehingga cuplikan tidak dilanjutkan pada utas UI nyata. Sebaliknya, cuplikan ini mengilustrasikan aturan secara konseptual: jika UI SynchronizationContext mengalir melintasi await titik, maka await di dalam delegasi yang diteruskan ke Task.Run akan melihat konteks UI tersebut sebagai Current. Kelanjutan setelah await DownloadAsync() kemudian akan mengirim kembali ke utas UI, menyebabkan Compute(data) dijalankan pada utas UI alih-alih pada kumpulan utas. Perilaku itu mengalahkan tujuan panggilan Task.Run.
Karena runtime menghentikan aliran di ExecutionContext, elemen await dalam Task.Run tidak mewarisi konteks UI luar, dan proses lanjutan tetap berjalan di kumpulan utas sesuai yang direncanakan.
RINGKASAN
| Aspek | ExecutionContext | Konteks Sinkronisasi |
|---|---|---|
| Purpose | Membawa keadaan lingkungan di seluruh batas proses asinkron | Mewakili penjadwal target (di mana kode harus berjalan) |
| Diambil oleh | Pembangun metode asinkron (infrastruktur) | Penanti tugas (await task) |
| Apakah aliran berhenti saat menunggu? | Ya, selalu | Tidak—diambil dan diunggah ke, tidak ditransfer |
| API Pembatalan |
ExecutionContext.SuppressFlow (tingkat lanjut; jarang diperlukan) |
ConfigureAwait(false) |
| Cakupan | Semua yang dapat ditunggu |
Task dan Task<TResult> (awaiter kustom dapat menambahkan logika serupa) |