ConfigureAwait وسياق المزامنة
افهموا SynchronizationContext، واستخدموا ConfigureAwait(false) في شيفرة المكتبات، وتجنّبوا حالات الجمود في التدفقات غير المتزامنة.
ConfigureAwait وسياق المزامنة درس مجاني في C# Academy على CoddyKit. هذا هو الدرس 4 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في C# Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة C# Academy 4 دروس في المجموع.
ما هو SynchronizationContext؟
يتحكم SynchronizationContext في كيفية جدولة كود المتابعة بعد await. ففي تطبيقات واجهة المستخدم (WPF وWinForms)، يضمن تنفيذ تحديثات واجهة المستخدم على خيط واجهة المستخدم. أما في ASP.NET Core، فلا يوجد سياق مزامنة (إذ أزيل). ويُعد فهم ذلك أساسيًا لكتابة كود غير متزامن صحيح.
كيفية عمل ConfigureAwait
يخبر .ConfigureAwait(false) الـ awaiter بعدم التقاط SynchronizationContext الحالي وعدم استئناف التنفيذ عليه. بل تُشغَّل المتابعة على أي مؤشر ترابط متاح من مجمّع مؤشرات الترابط.
// Default: captures context (resumes on original context)
var data = await FetchDataAsync();
UpdateUI(data); // runs on UI thread — correct for UI apps
// ConfigureAwait(false): doesn't capture context
var data2 = await FetchDataAsync().ConfigureAwait(false);
// Runs on any thread — DON'T access UI thread-only objects hereالنمط التقليدي للتوقف المتبادل
يؤدي حظر Task باستخدام .Result أو .Wait() أثناء الاحتفاظ بسياق مزامنة إلى توقف متبادل تقليدي — إذ تنتظر المتابعة السياقَ المحجوزَ بانتظار المتابعة.
// DEADLOCK in WPF/WinForms or ASP.NET classic:
string result = GetDataAsync().Result; // blocks UI thread
// GetDataAsync() tries to resume on UI thread after await
// UI thread is blocked by .Result
// DEADLOCK!
// Fix 1: await properly (never block)
string result = await GetDataAsync();
// Fix 2: use ConfigureAwait(false) in the library
public async Task<string> GetDataAsync()
=> await client.GetStringAsync(url).ConfigureAwait(false);استخدام ConfigureAwait(false) في المكتبات
ينبغي أن تستخدم شيفرة المكتبات غير المعتمدة على السياق دائمًا ConfigureAwait(false) مع كل عملية await. فهذا يمنع حالات التوقف المتبادل عندما يستدعي المتصلون المكتبة من بيئة حساسة للسياق.
// Library code — always ConfigureAwait(false)
public async Task<User?> GetUserAsync(int id)
{
var json = await client
.GetStringAsync($"/users/{id}")
.ConfigureAwait(false);
var user = JsonSerializer.Deserialize<User>(json);
return user;
}
// Application code (has context) — leave ConfigureAwait default
public async Task LoadUserProfileAsync(int id)
{
var user = await userService.GetUserAsync(id); // context captured
ProfileLabel.Text = user?.Name; // safe to update UI
}ASP.NET Core: لا يوجد SynchronizationContext
لا يحتوي ASP.NET Core عمدًا على SynchronizationContext. تُشغَّل المتابعات على مجمّع مؤشرات الترابط. ويُعد ConfigureAwait(false) عمليًا بلا تأثير، لكنه يظل عادة جيدة في الشيفرة المشتركة مع أطر عمل أخرى.
// In ASP.NET Core, both are equivalent:
var data = await service.GetAsync(); // fine
var data2 = await service.GetAsync().ConfigureAwait(false); // also fine
// Context-free means: no deadlock risk from .Result in ASP.NET Core
// BUT: mixing with libraries that assume a context is still risky
// BEST PRACTICE: still use ConfigureAwait(false) in library codeمقارنة جدولة مجمّع مؤشرات الترابط بجدولة السياق
في غياب السياق، تُجدول المتابعات بواسطة TaskScheduler — وعادةً بواسطة ThreadPoolTaskScheduler. أما مع سياق واجهة المستخدم، فتُرسل إلى حلقة رسائل واجهة المستخدم.
// Check current synchronization context:
Console.WriteLine(SynchronizationContext.Current?.GetType().Name
?? "No context (thread pool)");
// In a WPF event handler: "DispatcherSynchronizationContext"
// In ASP.NET Core: null (no context)
// In a unit test with xUnit: "AsyncTestSyncContext"
// After ConfigureAwait(false):
await Task.Delay(1).ConfigureAwait(false);
Console.WriteLine(SynchronizationContext.Current?.GetType().Name
?? "No context"); // null — context was abandonedاستخدام ConfigureAwait في الحلقات
عند استخدام await داخل حلقة، تُشغَّل متابعة كل تكرار في السياق الذي جرى التقاطه، ما لم يُستخدم ConfigureAwait(false). وفي شيفرة المكتبات، طبّق ذلك باستمرار مع كل await داخل الحلقة.
// Library loop: ConfigureAwait(false) on every await
public async Task ProcessBatchAsync(IEnumerable<int> ids)
{
foreach (var id in ids)
{
var item = await FetchItemAsync(id).ConfigureAwait(false);
await SaveItemAsync(item).ConfigureAwait(false);
}
}ValueTask وConfigureAwait
يدعم ValueTask أيضًا ConfigureAwait(false). طبّقه باستمرار وفق القواعد نفسها المتبعة مع Task.
public async ValueTask<string> GetFromCacheAsync(string key)
{
// ValueTask with ConfigureAwait(false)
var raw = await _store.GetAsync(key).ConfigureAwait(false);
return raw ?? "";
}استخدام Task.Run لتجنّب السياق
استخدم Task.Run لنقل العمل المرتبط بالمعالج إلى مجمّع مؤشرات الترابط، بما يضمن عدم تشغيله مطلقًا على مؤشر ترابط يحتفظ بسياق، مثل مؤشر ترابط واجهة المستخدم.
// WRONG in UI app: blocks UI thread
void ButtonClick(object? sender, EventArgs e)
{
var result = HeavyComputation(); // blocks UI
Label.Text = result;
}
// CORRECT: offload to thread pool
async void ButtonClick(object? sender, EventArgs e)
{
var result = await Task.Run(() => HeavyComputation());
// Continuation captured UI context: safe to update UI
Label.Text = result;
}من واقع عملي: مكتبة HttpClient مع ConfigureAwait
تستخدم مكتبة تغليف HTTP مخصّصة للإنتاج ConfigureAwait(false) في جميع المواضع تقريبًا، لتكون آمنة عند استدعائها من أي سياق.
public class ApiClient
{
private readonly HttpClient _http;
private readonly JsonSerializerOptions _opts = new(JsonSerializerDefaults.Web);
public ApiClient(HttpClient http) => _http = http;
public async Task<T> GetAsync<T>(string path, CancellationToken ct = default)
{
using var response = await _http
.GetAsync(path, ct)
.ConfigureAwait(false);
response.EnsureSuccessStatusCode();
return await response.Content
.ReadFromJsonAsync<T>(_opts, ct)
.ConfigureAwait(false)
?? throw new InvalidOperationException("Null response");
}
}تحقق سريع
لماذا يؤدي استدعاء .Result على Task في تطبيق WPF أو WinForms غالبًا إلى توقف متبادل؟
مراجعة: ConfigureAwait وSynchronizationContext
أهم النقاط:
SynchronizationContext: يجدول المتابعات للعودة إلى مؤشر ترابط محدد، مثل مؤشر ترابط واجهة المستخدمConfigureAwait(false): يلغي التقاط السياق، ويستأنف التنفيذ على مجمّع مؤشرات الترابط- شيفرة المكتبات: استخدم دائمًا
ConfigureAwait(false)لمنع حالات التوقف المتبادل - شيفرة التطبيقات التي تحتوي على واجهة مستخدم: استخدم
awaitالافتراضي للعودة إلى مؤشر ترابط واجهة المستخدم - ASP.NET Core: لا يحتوي على SynchronizationContext، لكن يظل استخدامه ممارسة جيدة في المكتبات المشتركة
- لا تحظر
Taskباستخدام.Result/.Wait()في التطبيقات الحساسة للسياق
الأسئلة الشائعة
هل درس «ConfigureAwait وسياق المزامنة» مجاني؟
نعم — نص درس «ConfigureAwait وسياق المزامنة» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة C# Academy، انتقل إلى CoddyKit PRO. تتضمن دورة C# Academy 4 دروس في المجموع.
ماذا ستتعلم في «ConfigureAwait وسياق المزامنة»؟
افهموا SynchronizationContext، واستخدموا ConfigureAwait(false) في شيفرة المكتبات، وتجنّبوا حالات الجمود في التدفقات غير المتزامنة. تتمرن على C# Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ C# Academy؟
لا تُشترط خبرة سابقة. C# Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 4 من أصل 4.
كم من الوقت يستغرق درس «ConfigureAwait وسياق المزامنة»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس C# Academy هذا؟
نعم. كل درس في C# Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- IAsyncEnumerable وawait foreach
- System.Threading.Channels
- ValueTask وتجنّب التخصيصات
- ConfigureAwait وسياق المزامنة