it-swarm.asia

أفضل ممارسة للاتصال بـ ConfigureAwait لجميع التعليمات البرمجية من جانب الخادم

عندما يكون لديك رمز من جانب الخادم (أي بعض ApiController) وتكون وظائفك غير متزامنة - لذا فإنها تُرجع Task<SomeObject> - فهل تعتبر أفضل ممارسة تنتظرها في أي وقت تقوم فيها بالوظائف التي تستدعيها ConfigureAwait(false)؟

لقد قرأت أنه أكثر فاعلية لأنه لا يلزم تبديل سياقات سلاسل الرسائل مرة أخرى إلى سياق سلسلة الرسائل الأصلي. ومع ذلك ، مع ASP.NET Web Api ، إذا كان طلبك يرد على مؤشر ترابط واحد ، وتنتظر بعض الوظائف وتتصل بـ ConfigureAwait(false) والتي من المحتمل أن تضعك في مؤشر ترابط مختلف عند إرجاع النتيجة النهائية لوظيفة ApiController الخاصة بك.

لقد قمت بكتابة مثال على ما أتحدث عنه أدناه:

public class CustomerController : ApiController
{
    public async Task<Customer> Get(int id)
    {
        // you are on a particular thread here
        var customer = await SomeAsyncFunctionThatGetsCustomer(id).ConfigureAwait(false);

        // now you are on a different thread!  will that cause problems?
        return customer;
    }
}
468
Arash Emami

تحديث: ASP.NET Core ليس لديه SynchronizationContext . إذا كنت تستخدم ASP.NET Core ، فلا يهم ما إذا كنت تستخدم ConfigureAwait(false) أم لا.

بالنسبة إلى ASP.NET "كامل" أو "كلاسيكي" أو أيًا كان ، لا يزال يتم تطبيق باقي هذه الإجابة.

النشر الأصلي (لـ ASP.NET غير الأساسي):

هذا الفيديو من قبل فريق ASP.NET لديه أفضل المعلومات حول استخدام async على ASP.NET.

لقد قرأت أنه أكثر فاعلية لأنه لا يلزم تبديل سياقات سلاسل الرسائل مرة أخرى إلى سياق سلسلة الرسائل الأصلي.

هذا صحيح مع تطبيقات واجهة المستخدم ، حيث يوجد مؤشر ترابط واجهة المستخدم واحد فقط يجب عليك "مزامنته" مرة أخرى.

في ASP.NET ، يكون الموقف أكثر تعقيدًا بعض الشيء. عندما يستأنف أسلوب async التنفيذ ، فإنه يمسك سلسلة من تجمع مؤشرات ترابط ASP.NET. إذا قمت بتعطيل التقاط السياق باستخدام ConfigureAwait(false) ، فسيستمر مؤشر الترابط في تنفيذ الطريقة مباشرةً. إذا لم تقم بتعطيل التقاط السياق ، فستعيد سلسلة الرسائل إدخال سياق الطلب ثم تواصل تنفيذ الطريقة.

حتى ConfigureAwait(false) لا يوفر لك قفزة مؤشر ترابط في ASP.NET؛ إنه يوفر عليك إعادة إدخال سياق الطلب ، لكن هذا عادة ما يكون سريعًا جدًا. ConfigureAwait(false)يمكن تكون مفيدة إذا كنت تحاول القيام بعدد صغير من المعالجة المتوازية للطلب ، ولكن TPL حقًا هو الأنسب لمعظم تلك السيناريوهات.

ومع ذلك ، مع ASP.NET Web Api ، إذا كان طلبك يرد على مؤشر ترابط واحد ، وتنتظر بعض الوظائف وتدعو ConfigureAwait (false) التي من المحتمل أن تضعك في مؤشر ترابط مختلف عند إرجاع النتيجة النهائية لوظيفة ApiController .

في الواقع ، مجرد القيام بـ await يمكنه فعل ذلك. بمجرد أن تصل طريقة async إلى await ، يتم حظر method لكن thread ترجع إلى تجمع مؤشرات الترابط. عندما تكون الطريقة جاهزة للمتابعة ، يتم انتزاع أي مؤشر ترابط من تجمع مؤشرات الترابط واستخدامه لاستئناف الطريقة.

الفرق الوحيد الذي يجعل ConfigureAwait في ASP.NET هو ما إذا كان مؤشر الترابط هذا يدخل سياق الطلب عند استئناف الأسلوب.

لدي المزيد من المعلومات الأساسية في مقالتي MSDN على SynchronizationContext وبلدي async blog blog .

516
Stephen Cleary

إجابة مختصرة عن سؤالك: لا ، لا يجب عليك الاتصال بـ ConfigureAwait(false) على مستوى التطبيق بهذا الشكل.

إصدار TL ؛ DR للإجابة الطويلة: إذا كنت تكتب مكتبة لا تعرف فيها مستهلكك ولا تحتاج إلى سياق التزامن (الذي لا ينبغي أن تكون في مكتبة أعتقد) ، يجب عليك دائمًا استخدام ConfigureAwait(false). خلاف ذلك ، قد يواجه مستهلكو مكتبتك جمودًا عن طريق استهلاك طرقك غير المتزامنة بطريقة مانعة. هذا يعتمد على الوضع.

في ما يلي شرح أكثر تفصيلاً حول أهمية الأسلوب ConfigureAwait (اقتباس من منشور مدونتي):

عندما تنتظر طريقة تتضمن كلمة رئيسية تنتظر ، ينشئ المترجم مجموعة من التعليمات البرمجية نيابة عنك. أحد أغراض هذا الإجراء هو معالجة التزامن مع مؤشر ترابط واجهة المستخدم (أو الرئيسي). المكون الرئيسي لهذه الميزة هو SynchronizationContext.Current الذي يحصل على سياق التزامن لمؤشر الترابط الحالي. يتم تعبئة SynchronizationContext.Current اعتمادًا على البيئة التي تتواجد فيها. تبحث طريقة GetAwaiter of Task عن SynchronizationContext.Current. إذا لم يكن سياق التزامن الحالي خاليًا ، فسيتم إعادة نشر الاستمرارية التي يتم تمريرها إلى ذلك الانتظار إلى سياق المزامنة هذا.

عند استهلاك طريقة ، والتي تستخدم ميزات اللغة غير المتزامنة الجديدة ، بطريقة الحظر ، سوف ينتهي بك الأمر إلى طريق مسدود إذا كان لديك SynchronizationContext متاح. عندما تستهلك هذه الأساليب بأسلوب منع (في انتظار طريقة المهمة مع انتظار أو أخذ النتيجة مباشرة من خاصية النتيجة للمهمة) ، فإنك تحظر سلسلة الرسائل الرئيسية في نفس الوقت. عند اكتمال المهمة في النهاية داخل هذه الطريقة في threadpool ، ستستدعي المتابعة للنشر مرة أخرى إلى الخيط الرئيسي لأن SynchronizationContext.Current متاح والتقاطه. ولكن هناك مشكلة هنا: مؤشر ترابط واجهة المستخدم محظور ولديك حالة توقف تام!

أيضًا ، إليك مقالتان عظيمتان لكما لسؤالك:

أخيرًا ، يوجد فيديو قصير رائع من Lucian Wischik تمامًا حول هذا الموضوع: يجب أن تراعي أساليب مكتبة Async استخدام Task.ConfigureAwait (false) .

أتمنى أن يساعدك هذا.

122
tugberk

أكبر عائق وجدته في استخدام ConfigureAwait (false) هو أن ثقافة سلسلة الرسائل قد عادت إلى النظام الافتراضي. إذا قمت بتكوين ثقافة على سبيل المثال ...

<system.web>
    <globalization culture="en-AU" uiCulture="en-AU" />    
    ...

وأنت تستضيف على خادم تم تعيين ثقافته على en-US ، ثم ستجد قبل أن يسمى ConfigureAwait (false) CultureInfo.CurrentCulture سيعود en-AU وبعد أن تصبح en-US. أي.

// CultureInfo.CurrentCulture ~ {en-AU}
await xxxx.ConfigureAwait(false);
// CultureInfo.CurrentCulture ~ {en-US}

إذا كان التطبيق الخاص بك يقوم بأي شيء يتطلب تنسيقًا معينًا لثقافة البيانات ، فستحتاج إلى أن تضع في اعتبارها هذا عند استخدام ConfigureAwait (false).

15
Mick

لدي بعض الأفكار العامة حول تنفيذ Task:

  1. المهمة يمكن التخلص منها ولكننا ليس من المفترض أن نستخدم using.
  2. تم تقديم ConfigureAwait في 4.5. تم تقديم Task في 4.0.
  3. مؤشرات ترابط .NET دائمًا تُستخدم لتدفق السياق (انظر C # عبر دفتر CLR) ولكن في التنفيذ الافتراضي لـ Task.ContinueWith ، لم يتم تفعيلها/كان قد تم إدراك تبديل السياق لها وهو مكلف ويتم إيقاف تشغيله افتراضيًا.
  4. المشكلة هي أن مطور المكتبة يجب ألا يهتم بما إذا كان عملائه يحتاجون إلى تدفق السياق أم لا ، وبالتالي لا ينبغي أن يقرروا ما إذا كان يجب أن يتدفق السياق أم لا.
  5. [أضيف لاحقًا] حقيقة أنه لا توجد إجابة موثوقة ومرجع مناسب ونواصل القتال على هذا يعني أن شخصًا ما لم يقوم بعمله بشكل صحيح.

لقد حصلت على عدد قليل من منشورات حول هذا الموضوع ، لكن تعليقاتي - بالإضافة إلى إجابة Tugberk Nice - هو أنه يجب عليك تحويل جميع واجهات برمجة التطبيقات إلى غير متزامن وتدفق السياق بشكل مثالي. نظرًا لأنك تقوم بالمزامنة ، يمكنك ببساطة استخدام الاستمرارات بدلاً من الانتظار حتى لا يكون هناك أي طريق مسدود بسبب عدم انتظار الانتظار في المكتبة والحفاظ على التدفق حتى يتم الحفاظ على السياق (مثل HttpContext).

المشكلة هي عندما تكشف مكتبة عن واجهة برمجة تطبيقات متزامنة ولكنها تستخدم واجهة برمجة تطبيقات غير متزامنة أخرى - ومن ثم تحتاج إلى استخدام Wait()/Result في التعليمات البرمجية الخاصة بك.

7
Aliostad