مرحبا يا هبر! أقدم لكم ترجمة مقالة
أسئلة وأجوبة لـ ConfigureAwait من تأليف Stephen Taub.

await
إضافة
Async
/
await
إلى .NET منذ أكثر من سبع سنوات. كان لهذا القرار تأثير كبير ليس فقط على النظام البيئي .NET - بل ينعكس أيضًا في العديد من اللغات والأطر الأخرى. حاليًا ، تم تنفيذ العديد من التحسينات في .NET من حيث التركيبات اللغوية الإضافية باستخدام عدم التزامن ، وتم تنفيذ واجهات برمجة التطبيقات مع دعم غير متزامن ، وقد تم إجراء تحسينات أساسية في البنية التحتية بسبب عدم
async
/
await
مثل ساعة (على وجه الخصوص ، تم تحسين الأداء وقدرات التشخيص في. NET الأساسية).
يعد
ConfigureAwait
أحد مظاهر
async
/
await
الذي يستمر في طرح الأسئلة. آمل أن أتمكن من الإجابة على الكثير منهم. سأحاول جعل هذه المقالة قابلة للقراءة من البداية إلى النهاية ، وفي الوقت نفسه تنفيذها في نمط الإجابات على الأسئلة المتداولة (FAQ) بحيث يمكن الرجوع إليها في المستقبل.
للتعامل مع
ConfigureAwait
بالفعل ، سنعود قليلاً.
ما هو SynchronizationContext؟
وفقًا لوثائق
System.Threading.SynchronizationContext "توفر الوظيفة الأساسية لتوزيع سياق التزامن في نماذج التزامن المختلفة." هذا التعريف ليس واضحًا تمامًا.
في 99.9٪ من الحالات ،
SynchronizationContext
استخدام
SynchronizationContext
ببساطة كنوع ذو أسلوب
Post
افتراضي يقبل مفوضًا للتنفيذ غير المتزامن (هناك أعضاء افتراضيون آخرون في
SynchronizationContext
، لكنهم أقل شيوعًا ولن يتم مناقشتهم في هذه المقالة).
استدعاء الأسلوب
Post
من النوع الأساسي حرفياً
ببساطة ThreadPool.QueueUserWorkItem
لتنفيذ المفوض المتوفر بشكل غير متزامن. تخطي الأنواع المشتقة
Post
بحيث يمكن للمفوض التنفيذ في المكان المناسب في الوقت المناسب.
على سبيل المثال ، يحتوي Windows Forms على
نوع مشتق من SynchronizationContext يعيد تعريف
Post
لجعل المكافئ
Control.BeginInvoke
. هذا يعني أن أي مكالمة إلى طريقة
Post
هذه ستؤدي إلى استدعاء المفوض في مرحلة لاحقة في سلسلة الرسائل المرتبطة بالتحكم المقابل - ما يسمى بخيط واجهة المستخدم. في قلب نماذج ويندوز هو معالجة الرسائل Win32. يتم تنفيذ حلقة الرسائل في مؤشر ترابط واجهة المستخدم التي تنتظر فقط معالجة الرسائل الجديدة. يتم تشغيل هذه الرسائل من خلال حركة الماوس والنقرات وإدخال لوحة المفاتيح وأحداث النظام المتاحة للتنفيذ من قبل المفوضين ، إلخ. لذلك ، إذا كان لديك مثيل
SynchronizationContext
لمؤشر ترابط واجهة المستخدم في تطبيق Windows Forms ، فيجب عليك تمرير المفوض إلى طريقة
Post
أجل إجراء عملية فيه.
يحتوي Windows Presentation Foundation (WPF) أيضًا على
نوع مشتق من
SynchronizationContext
مع طريقة
Post
مهجورة بالمثل "توجه" المفوض إلى دفق واجهة المستخدم (باستخدام
Dispatcher.BeginInvoke
) ، مع تحكم WPF Dispatcher ، وليس التحكم في نماذج Windows.
ويحتوي Windows RunTime (WinRT) على
نوع مشتق من
SynchronizationContext
، والذي يضع المفوض أيضًا في
CoreDispatcher
مؤشر ترابط واجهة المستخدم باستخدام
CoreDispatcher
.
هذا هو ما يكمن وراء عبارة "تشغيل المفوض في موضوع واجهة المستخدم". يمكنك أيضًا تطبيق
SynchronizationContext
باستخدام طريقة
Post
وبعض التنفيذ. على سبيل المثال ، لا داعي للقلق بشأن مؤشر الترابط الذي يعمل عليه المفوض ، لكنني أريد أن أتأكد من أن أي طريقة
Post
تفويض في
SynchronizationContext
تعمل مع درجة محدودة من التوازي. يمكنك تطبيق
SynchronizationContext
مخصص بهذه الطريقة:
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(); } } }
يحتوي إطار عمل xUnit على
تطبيق مماثل
لـ SynchronizationContext. هنا يتم استخدامه لتقليل مقدار التعليمات البرمجية المرتبطة الاختبارات المتوازية.
المزايا هنا هي نفسها كما هي الحال مع أي تجريد: يتم توفير واجهة برمجة تطبيقات واحدة يمكن استخدامها في قائمة انتظار المفوض للتنفيذ بالطريقة التي يرغب فيها المبرمج ، دون الحاجة إلى معرفة تفاصيل التنفيذ. لنفترض أنني أكتب مكتبة حيث أحتاج إلى القيام ببعض الأعمال ثم الانتظار في قائمة الانتظار مرة أخرى إلى السياق الأصلي. للقيام بذلك ، أحتاج إلى التقاط
SynchronizationContext
الخاص به ، وعندما أكمل ما يلزم ، سأكون مضطرًا فقط لاستدعاء طريقة
Post
الخاصة بهذا السياق وتمريره كمفوض للتنفيذ. لست بحاجة إلى معرفة أنه بالنسبة لنماذج Windows ، يلزمك
Control
واستخدام
BeginInvoke
، أو WPF استخدم
BeginInvoke
من
Dispatcher
، أو بطريقة ما الحصول على السياق وقائمة الانتظار لـ xUnit. كل ما أحتاج إليه هو الاستيلاء على
SynchronizationContext
الحالي واستخدامه لاحقًا. للقيام بذلك ، يحتوي
SynchronizationContext
على خاصية
Current
. هذا يمكن تنفيذه على النحو التالي:
public void DoWork(Action worker, Action completion) { SynchronizationContext sc = SynchronizationContext.Current; ThreadPool.QueueUserWorkItem(_ => { try { worker(); } finally { sc.Post(_ => completion(), null); } }); }
يمكنك تعيين سياق خاص من الخاصية
Current
باستخدام الأسلوب
SynchronizationContext.SetSynchronizationContext
.
ما هو برنامج جدولة المهام؟
SynchronizationContext
عبارة عن تجريد شائع لـ "المجدول". بعض الأطر تستخدم التجريدات الخاصة بها لذلك ، و
System.Threading.Tasks
ليست استثناء. عندما يكون هناك مفوضون في
Task
التي يمكن وضعها في قائمة الانتظار وتنفيذها ، فإنها ترتبط مع
System.Threading.Tasks.TaskScheduler
. هناك أيضًا طريقة
Post
افتراضية
TaskScheduler
انتظار المفوض (يتم تنفيذ استدعاء المفوض باستخدام الآليات القياسية) ، ويوفر
QueueTask
طريقة
QueueTask
مجردة (يتم تنفيذ استدعاء مهمة باستخدام أسلوب
ExecuteTask
).
المجدول الافتراضي الذي يقوم بإرجاع
TaskScheduler.Default
هو تجمع
TaskScheduler.Default
ترابط. من
TaskScheduler
من الممكن أيضًا الحصول على أساليب
TaskScheduler
لتحديد وقت ومكان استدعاء
Task
. على سبيل المثال ، تتضمن المكتبات الأساسية نوع
System.Threading.Tasks.ConcurrentExclusiveSchedulerPair
. يوفر مثيل هذه الفئة
TaskScheduler
خصائص
TaskScheduler
:
ExclusiveScheduler
و
ConcurrentScheduler
. يمكن تنفيذ المهام المجدولة في
ConcurrentScheduler
بشكل متوازٍ ، ولكن مع مراعاة التقييد الذي تم تعيينه بواسطة
ConcurrentExclusiveSchedulerPair
عند إنشائه (مشابه لـ
MaxConcurrencySynchronizationContext
). لن يتم تنفيذ أي مهمة
ConcurrentScheduler
إذا تم تنفيذ المهمة في
ExclusiveScheduler
ولم يسمح إلا لمهمة حصرية واحدة بالعمل في وقت واحد. هذا السلوك مشابه جدًا لقفل القراءة / الكتابة.
مثل
SynchronizationContext
، يحتوي
TaskScheduler
على خاصية
Current
تقوم بإرجاع
TaskScheduler
الحالي. ومع ذلك ، بخلاف
SynchronizationContext
، فإنه يفتقر إلى طريقة لتعيين المجدول الحالي. بدلاً من ذلك ، يرتبط برنامج الجدولة بالمهمة الحالية. لذلك ، على سبيل المثال ، سيعرض هذا البرنامج
True
، حيث يتم تنفيذ lambda المستخدمة في
StartNew
في مثيل
ExclusiveScheduler
من
ConcurrentExclusiveSchedulerPair
،
TaskScheduler.Current
تثبيت
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(); } }
ومن المثير للاهتمام ، أن
TaskScheduler
يوفر أسلوب
FromCurrentSynchronizationContext
ثابت. تقوم الطريقة بإنشاء
TaskScheduler
جديدة
TaskScheduler
المهام للتنفيذ في سياق
SynchronizationContext.Current
المرتجع باستخدام أسلوب
Post
.
كيف هي SynchronizationContext و TaskScheduler المتعلقة انتظار؟
لنفترض أنك بحاجة إلى كتابة تطبيق واجهة المستخدم باستخدام زر. يؤدي الضغط على الزر إلى بدء تنزيل النص من موقع الويب وتعيينه على زر
Content
. يجب أن يكون الزر متاحًا فقط من واجهة المستخدم للدفق الذي يقع فيه ، وبالتالي ، عندما نقوم بتحميل التاريخ والوقت بنجاح ونريد وضعهما في
Content
الزر ، نحتاج إلى القيام بذلك من الدفق الذي يتحكم فيه. إذا لم يتحقق هذا الشرط ، فسنحصل على استثناء:
System.InvalidOperationException: ' , .'
يمكننا استخدام
SynchronizationContext
يدويًا لتعيين
Content
في سياق المصدر ، على سبيل المثال عبر
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()); }
ويمكننا استخدام
SynchronizationContext
مباشرة:
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); }); }
ومع ذلك ، يستخدم كلا الخيارين بشكل صريح رد اتصال. بدلاً من ذلك ، يمكننا استخدام
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; }
كل هذا "يعمل فقط" ويقوم بتكوين
Content
بنجاح في مؤشر ترابط واجهة المستخدم ، لأنه في حالة الإصدار الذي تم تنفيذه يدويًا أعلاه ، يشير انتظار المهمة افتراضيًا إلى
SynchronizationContext.Current
و
TaskScheduler.Current
. عندما "تتوقع" شيئًا ما في C # ، يقوم المحول البرمجي بتحويل رمز الاستقصاء (عن طريق استدعاء
GetAwaiter
) "المتوقع" (في هذه الحالة ، المهمة) إلى "انتظار" (
TaskAwaiter
). يكون "انتظار" مسؤولاً عن إرفاق رد اتصال (يُطلق عليه غالبًا "استمرار") والذي يعود إلى جهاز الحالة عند اكتمال الانتظار. يقوم بتنفيذ ذلك باستخدام السياق / المجدول الذي سجله أثناء تسجيل رد الاتصال. سنقوم بتحسين وتكوين بعض الشيء ، إنه شيء مثل هذا:
object scheduler = SynchronizationContext.Current; if (scheduler is null && TaskScheduler.Current != TaskScheduler.Default) { scheduler = TaskScheduler.Current; }
هنا ، يتم تحديد ما إذا كان
SynchronizationContext
TaskScheduler
أم لا ، وما إذا كان هناك
TaskScheduler
غير قياسي. إذا كان هناك واحد ، فعندما يكون رد الاتصال جاهزًا للمكالمة ، فسيتم استخدام المجدول الذي تم التقاطه ؛ إذا لم يكن الأمر كذلك ، فسيتم تنفيذ رد الاتصال كجزء من العملية التي تكمل المهمة المتوقعة.
ماذا تفعل ConfigureAwait (خطأ)
طريقة
ConfigureAwait
ليست خاصة: لم يتم التعرف عليها بأي طريقة معينة من قبل المترجم أو وقت التشغيل. هذه طريقة عادية تقوم بإرجاع بنية (
ConfiguredTaskAwaitable
- يلتف المهمة الأصلية) وتتخذ قيمة منطقية. تذكر أنه يمكن استخدام
await
مع أي نوع يقوم بتنفيذ النموذج الصحيح. إذا تم إرجاع نوع آخر ، فهذا يعني أنه عندما يحصل المترجم على طريقة
GetAwaiter
(جزء من النموذج) من المثيلات ، لكنه يفعل ذلك من النوع الذي تم إرجاعه من
ConfigureAwait
، وليس من المهمة مباشرة. هذا يسمح لك بتغيير سلوك
await
هذا الانتظار الخاص.
انتظار النوع الذي يتم إرجاعه بواسطة
ConfigureAwait(continueOnCapturedContext: false)
بدلاً من انتظار
Task
يؤثر مباشرة على تطبيق التقاط السياق / المجدول الموضح أعلاه. يصبح المنطق شيئًا مثل هذا:
object scheduler = null; if (continueOnCapturedContext) { scheduler = SynchronizationContext.Current; if (scheduler is null && TaskScheduler.Current != TaskScheduler.Default) { scheduler = TaskScheduler.Current; } }
بمعنى آخر ، يعني تحديد
false
، حتى إذا كان هناك سياق أو برنامج جدولة حاليين لرد الاتصال ، أنه غائب.
لماذا أحتاج إلى استخدام ConfigureAwait (خطأ)؟
ConfigureAwait(continueOnCapturedContext: false)
لمنع فرض معاودة الاتصال في سياق المصدر أو المجدول المصدر. هذا يعطينا العديد من المزايا:
تحسين الأداء. هناك مقدار كبير من الانتظار في قائمة انتظار لاستدعاء ، على عكس الاتصال فقط ، لأن هذا يتطلب عمل إضافي (وتخصيص إضافي عادة). بالإضافة إلى ذلك ، لا يمكننا استخدام التحسين في وقت التشغيل (يمكننا تحسين أكثر عندما نعرف بالضبط كيف سيتم استدعاء رد الاتصال ، ولكن إذا تم نقله إلى تنفيذ تعسفي للتجريد ، فإن هذا يفرض قيودًا في بعض الأحيان). بالنسبة للأقسام المحملة بكثافة ، يمكن أن تزيد التكاليف الإضافية للتحقق من
SynchronizationContext
الحالي و
TaskScheduler
الحالي (كلاهما ينطوي على الوصول إلى التدفقات الثابتة) بشكل كبير. إذا كانت التعليمة البرمجية بعد
await
لا تتطلب التنفيذ في السياق الأصلي ، باستخدام
ConfigureAwait(false)
، يمكن تجنب كل هذه النفقات ، نظرًا لأنه لا يحتاج إلى وضع قائمة الانتظار دون داع ، ويمكنه استخدام جميع التحسينات المتاحة ، كما يمكنه تجنب الوصول غير الضروري إلى إحصائيات التدفق.
منع الجمود. فكر في طريقة المكتبة التي
await
استخدامها لتنزيل شيء من الشبكة. يمكنك استدعاء هذه الطريقة وحظرها بشكل متزامن ، في انتظار إكمال المهمة ، على سبيل المثال ، باستخدام
.Wait()
أو
.Result
أو
.GetAwaiter()
.GetResult()
. الآن ، ضع في اعتبارك ما يحدث في حالة حدوث المكالمة عندما يحدد
SynchronizationContext
الحالي عدد العمليات فيه إلى 1 باستخدام
MaxConcurrencySynchronizationContext
بشكل صريح ، أو ضمنيًا ، إذا كان سياقًا باستخدام مؤشر ترابط واحد لاستخدامه (على سبيل المثال ، مؤشر ترابط واجهة المستخدم). وبالتالي ، يمكنك استدعاء الأسلوب في مؤشر ترابط واحد ، ثم حظره ، في انتظار اكتمال العملية. يبدأ التنزيل عبر الشبكة وينتظر استكماله. بشكل افتراضي ،
Task
انتظار
Task
التقاط
SynchronizationContext
الحالي (وفي هذه الحالة) ، وعند اكتمال التنزيل من الشبكة ، سيتم وضعه في قائمة الانتظار مرة أخرى في رد الاتصال
SynchronizationContext
، والذي سوف يستدعي باقي العملية. ولكن مؤشر الترابط الوحيد الذي يمكنه التعامل مع رد الاتصال في قائمة الانتظار محظور حاليًا أثناء انتظار اكتمال العملية. ولن تكتمل هذه العملية حتى تتم معالجة رد الاتصال. طريق مسدود! يمكن أن يحدث حتى عندما لا يقتصر السياق على التزامن على 1 ، ولكن الموارد محدودة بطريقة أو بأخرى. تخيل الموقف نفسه ، فقط بقيمة 4 لـ
MaxConcurrencySynchronizationContext
. بدلاً من تنفيذ العملية مرة واحدة ، نحن في قائمة الانتظار 4 مكالمات إلى السياق. يتم إجراء كل مكالمة وأقفال تحسبا لاستكمالها. يتم الآن حظر جميع الموارد انتظارًا لاستكمال الأساليب غير المتزامنة ، والشيء الوحيد الذي سيسمح لها بالإكمال هو إذا تمت معالجة عمليات الاسترجاعات الخاصة بهم بواسطة هذا السياق. ومع ذلك ، فهو بالفعل مشغولة بالكامل. حالة توقف تام مرة أخرى. إذا كانت طريقة المكتبة تستخدم
ConfigureAwait(false)
بدلاً من ذلك ، فلن تضع قائمة انتظار في رد الاتصال في السياق الأصلي ، مما يؤدي إلى تجنب البرامج النصية لحالة توقف تام.
هل أحتاج إلى استخدام ConfigureAwait (صواب)؟
لا ، ما لم تكن بحاجة إلى الإشارة صراحة إلى أنك لا تستخدم
ConfigureAwait(false)
(على سبيل المثال ، لإخفاء تحذيرات التحليل الثابت ، وما إلى ذلك).
ConfigureAwait(true)
لا يفعل شيئًا مهمًا. إذا قارنت
await task
await task.ConfigureAwait(true)
، فستكون متطابقة وظيفيًا. وبالتالي ، إذا كان
ConfigureAwait(true)
موجودًا في التعليمات البرمجية ، فيمكن حذفه دون أي عواقب سلبية.
تأخذ طريقة
ConfigureAwait
قيمة منطقية ، حيث قد تحتاج في بعض الحالات إلى تمرير متغير للتحكم في التكوين. ولكن في 99٪ من الحالات ، يتم تعيين القيمة على "خطأ" ،
ConfigureAwait(false)
.
متى يجب استخدام ConfigureAwait (خطأ)؟
يعتمد ذلك على ما إذا كنت تقوم بتطبيق رمز مستوى التطبيق أو رمز مكتبة للأغراض العامة.
عند كتابة التطبيقات ، عادة ما تكون هناك حاجة إلى بعض السلوك الافتراضي. إذا قام طراز / بيئة التطبيق (على سبيل المثال ، Windows Forms و WPF و ASP.NET Core) بنشر
SynchronizationContext
خاص ، فمن المؤكد أن هناك سبب وجيه لذلك: فهذا يعني أن الكود يسمح لك بالاهتمام بسياق المزامنة للتفاعل المناسب مع طراز / بيئة التطبيق. على سبيل المثال ، إذا كتبت معالج أحداث في أحد تطبيقات Windows Forms ، أو اختبارًا في xUnit ، أو رمزًا في وحدة تحكم ASP.NET MVC ، بغض النظر عما إذا كان طراز التطبيق قد نشر
SynchronizationContext
، فأنت بحاجة إلى استخدام
SynchronizationContext
حالة وجوده. هذا يعني أنه إذا تم استخدام كل من
ConfigureAwait(true)
await
، فيتم إعادة عمليات الاسترجاعات / الاستجابات إلى السياق الأصلي - كل شيء يسير كما ينبغي. من هنا يمكنك صياغة قاعدة عامة:
إذا كتبت رمز مستوى التطبيق ، فلا تستخدم ConfigureAwait(false)
. دعنا نعود إلى معالج النقرات:
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
يجب تنفيذ
downloadBtn.Content = text
في السياق الأصلي. إذا انتهكت التعليمات البرمجية هذه القاعدة واستخدمت
ConfigureAwait (false)
بدلاً من ذلك ، فلن يتم استخدامها في السياق الأصلي:
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);
هذا سوف يؤدي إلى سلوك غير لائق. الأمر نفسه ينطبق على التعليمات البرمجية في تطبيق ASP.NET كلاسيكي يعتمد على
HttpContext.Current
. عند استخدام
ConfigureAwait(false)
المحتمل أن تؤدي محاولة لاحقة لاستخدام دالة
Context.Current
إلى حدوث مشكلات.
هذا هو ما يميز المكتبات العامة الغرض. إنها عالمية بشكل جزئي لأنها لا تهتم بالبيئة التي يتم استخدامها فيها. يمكنك استخدامها من تطبيق ويب أو من تطبيق عميل أو من اختبار - لا يهم ، نظرًا لأن رمز المكتبة غير ملائم لطراز التطبيق الذي يمكن استخدامه فيه. يعني Agnostic أيضًا أن المكتبة لن تفعل أي شيء للتفاعل مع طراز التطبيق ، على سبيل المثال ، لن تتمكن من الوصول إلى عناصر تحكم واجهة المستخدم ، لأن مكتبة الأغراض العامة لا تعرف شيئًا عنها. نظرًا لعدم وجود حاجة إلى تشغيل التعليمات البرمجية في أي بيئة معينة ، يمكننا تجنب فرض الاستمرارية / عمليات الاسترجاعات على السياق الأصلي ، ونقوم بذلك باستخدام
ConfigureAwait(false)
، مما يمنحنا مزايا أداء ويزيد من الموثوقية. يؤدي بنا ذلك إلى ما يلي:
إذا كنت تكتب رمز مكتبة للأغراض العامة ، فاستخدم ConfigureAwait(false)
. هذا هو السبب في أن كل (أو كل شيء تقريبًا) في مكتبات وقت تشغيل .NET Core تستخدم ConfigureAwait (false) ؛ مع بعض الاستثناءات ، والتي من المحتمل أن تكون أخطاء ، سيتم إصلاحها. ,
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)?
لا. , .
:
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
, , . , , , , . , , . , . و هكذا. .
ConfigureAwait(false) GetAwaiter ().GetResult ()?
لا.
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)?
ربما. , «». , , , ,
SynchronizationContext
TaskScheduler
, , . , , .
, .NET Core ConfigureAwait (false). ?
ليس هكذا. .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?
نعم. .
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 .
, ,
.