ConfigureAwait: الأسئلة المتداولة

مرحبا يا هبر! أقدم لكم ترجمة مقالة أسئلة وأجوبة لـ 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); //  downloadBtn.Content = text; } 

هذا سوف يؤدي إلى سلوك غير لائق. الأمر نفسه ينطبق على التعليمات البرمجية في تطبيق 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(); //    SomeCoolSyncCtx }); 

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(); // await'      } finally { SynchronizationContext.SetSynchronizationContext(old); } await t; //  -     


, 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)) { ... } 

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

, , .

Source: https://habr.com/ru/post/ar482354/


All Articles