محتويات الدورة

ASP.NET Core / المتوسط: البيانات والهوية والعمل غير المتزامن / الدرس 12

الطلبات غير المتزامنة والإلغاء وعمل الخلفية

أبق كل عملية غير متزامنة تحت الملاحظة وضمن حدود، عبر إلغاء الطلب والاعتماديات المشتركة ونطاقات الخلفية.

وقت القراءة التقريبي: 5 دقيقةتاريخ مراجعة المحتوى
في هذه الصفحة

أهداف الدرس

  • مرر RequestAborted إلى العمليات المعنية.
  • حد العمل النشط والعمل المنتظر في الطابور.
  • افصل مدة حياة الطلب عن معالجة الخلفية ذات الملكية المستقلة.

لا يجعل الانتظار غير المتزامن الاعتماديات آمنة أو غير محدودة

تتيح await تركيب اكتمال العمليات دون حجب خيط أثناء انتظار غير متزامن مدعوم. لا توازي الدالة تلقائيا، ولا تجعل تبعية ذات نطاق آمنة للاستخدام المتزامن بين الخيوط. يمكن لاستخدام .Result أو Wait المتزامنين على عمل الطلب حجب الخيوط والمساهمة في تجويعها أو في تعطل تبادلي مرتبط بالسياق. فضّل مسارا قائما على await، وقس الحجب الفعلي قبل ضبط إعدادات الخيوط.

ينتمي HttpContext إلى الطلب. لا تحتفظ به في خدمة مفردة النسخة أو عمل خلفية، ولا تستخدمه بالتزامن كحاوية مشتركة عشوائية. انسخ فقط البيانات الآمنة التي تحتاجها عملية ذات ملكية منفصلة، مع سياسة صريحة للصلاحيات ومدة الحياة. لا يضمن اكتمال الاستجابة أن كل مهمة بدأت قد انتهت.

إلغاء الطلب آلية تعاونية

يسند البرنامج الكامل الأول رمز RequestAborted اصطناعيا، ثم يلغيه وينتظر تأخيرا يراعي الرمز. يتحقق من تركيب الإلغاء دون انقطاع عميل أو شبكة. يجب أن تمرر العملية الفعلية الرمز أو تراقبه في كل مرحلة حجب معنية. لا يوقف فحصه مرة عند الدخول استدعاء لاحقا لا يراقب رمزا.

لا ينتظر طلب الإلغاء بحد ذاته اكتمال المهام، ولا يتراجع عن آثار ثبتت. انتظر العمل المملوك قبل التخلص من التدفقات أو السياقات أو النطاقات. ميّز الإلغاء المرتبط بالرمز المقصود عن الإخفاقات الأخرى، وقرر كيف تبلغ نقطة النهاية عن طلب قد يكون عميله قد غادر بالفعل.

csharp ملاحظة إلغاء طلب اصطناعي
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;

using var source = new CancellationTokenSource();
var context = new DefaultHttpContext { RequestAborted = source.Token };
source.Cancel();
try { await Task.Delay(1000, context.RequestAborted); }
catch (OperationCanceledException exception) when (exception.CancellationToken == source.Token)
{ Console.WriteLine("request work cancelled and observed"); }
الناتج المتوقع
request work cancelled and observed

يلغي الرمز المعيّن التأخير المنتظر؛ ولا يُفتح طلب أو اتصال بعميل.

حد التزامن مع الاستمرار في ملاحظة كل عمل بدأ

يستخدم البرنامج الثاني semaphore حول أربع عمليات اصطناعية وينتظر جميع المهام. يتحقق من صحة النتائج ومن بقاء العدد النشط الملاحظ ضمن اثنتين. القيم والحد مدخلات تعليمية وليست حدا عاما للطلبات. يقيد semaphore العمل النشط، لكن مجموعة غير محدودة من المهام المنتظرة قد تظل تحتفظ بالذاكرة.

حدد القبول وسعة الطابور والمهلة والإلغاء وسياسة الرفض معا. لا تحرر تصريح semaphore إلا بعد نجاح الحصول عليه، وأبقه حيا حتى ينتهي جميع مستخدميه. تجنب إطلاق عمل ثم نسيانه دون جهة تراقب فشله. تحتاج المحاولات إلى ميزانيات محدودة ومنع تكرار الأثر، لا إلى مزيد من التزامن الخفي.

csharp فحص عمل اصطناعي غير متزامن محدود
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

using var gate = new SemaphoreSlim(2);
object sync = new object();
int active = 0, peak = 0;
Task<int>[] work = Enumerable.Range(1, 4).Select(ProcessAsync).ToArray();
int[] results = await Task.WhenAll(work);
Console.WriteLine($"sum={results.Sum()} withinLimit={peak <= 2}");

async Task<int> ProcessAsync(int value)
{
    await gate.WaitAsync();
    try
    {
        lock (sync) { active++; peak = Math.Max(peak, active); }
        await Task.Yield();
        return value * value;
    }
    finally
    {
        lock (sync) active--;
        gate.Release();
    }
}
الناتج المتوقع
sum=30 withinLimit=True

يبلغ مجموع المربعات 30، ولا يتجاوز العدد النشط المقاس محليا التصريحين المتاحين.

يحتاج العامل المستضاف إلى نطاقه وعقد إغلاقه

توفر BackgroundService والخدمات المستضافة تكاملا مع دورة الحياة، لا تسليما دائما للعمل بحد ذاتها. ينبغي للعامل استخدام وحدات محدودة، وإنشاء النطاقات المناسبة، وملاحظة الاستثناءات، وتمرير إلغاء الإغلاق، والانتهاء أو التوقف وفق السياسة. قد يضيع العمل الموضوع في ذاكرة العملية عند إعادة التشغيل؛ لذا تحتاج الوظيفة المطلوب دوامها إلى تصميم مناسب للحفظ والطابور.

لا تحقن DbContext ذا نطاق طلب، ولا تلتقط HttpContext، داخل عامل مفرد النسخة. احصل على الاعتماديات ذات النطاق داخل نطاق صالح تملكه، وانتظر مستخدميها قبل التخلص منه. حدد المحاولات والعمل الذي يفشل باستمرار ونقل الملكية وجاهزية البدء والإغلاق. لا تكتمل مهمة خلفية لمجرد أن الطلب الذي بدأها أعاد 202.

المهلة والإلغاء ليسا بروتوكولا لذرية الآثار

قد تنتهي مهلة طلب بعد تثبيت قاعدة بيانات أو وقوع أثر خارجي. وقد يعيد المستدعي المحاولة دون معرفة نجاح المحاولة الأولى. حدد هوية العملية ومنع تكرار الأثر أو تسويته، والنقطة التي لا يعود بعدها ممكنا الادعاء بصدق أن الإلغاء يعني عدم حدوث شيء. لإعداد برمجية مهل الطلبات الوسيطة وسلوكها تفاصيل مرتبطة بالإصدار؛ ولا يفرض حقل CancellationToken وحده موعدا نهائيا.

اختبر الإلغاء قبل القبول، وأثناء الانتظار، وخلال العمل، وبعد حد التثبيت، باعتماديات اصطناعية. اطلب دليلا على الاكتمال والتنظيف، لا مجرد استثناء إلغاء عاد. تبدأ الأمثلة مهاما محلية محدودة فقط، ولا تبدأ خدمة مستضافة مهيأة أو قاعدة بيانات أو مستمع HTTP أو استدعاء خارجيا.

الأخطاء الشائعة وإصلاحها

يطلق طلب مهمة ويتخلص من نطاقها فورا.

لماذا يحدث؟
اختلط بدء المهمة باكتمالها.
كيف تصلحه؟
انتظر العمل داخل ملكيته، أو انقل المسؤولية إلى دورة حياة مستقلة ذات مالك.
كيف تتجنب تكراره؟
اختبر الوصول إلى الاعتمادية بعد استئناف التنفيذ من نقطة انتظار.

يُحرر semaphore بعد إلغاء محاولة الحصول على تصريح.

لماذا يحدث؟
يفترض التنظيف أن التصريح قد اكتُسب.
كيف تصلحه؟
لا تدخل try/finally المسؤولة عن التحرير إلا بعد نجاح WaitAsync.
كيف تتجنب تكراره؟
اختبر القبول برمز أُلغي مسبقا.

تُعامل استجابة 202 كعمل دائم ومكتمل.

لماذا يحدث؟
اختلط القبول بالحفظ وباكتمال عملية العمل.
كيف تصلحه؟
حدد ديمومة الطابور وعقد الحالة والفشل.
كيف تتجنب تكراره؟
اختبر متطلبات إعادة التشغيل وإعادة المعالجة في النظام المقصود.

اختبر فهمك

1. ينشئ معالج عشرة آلاف مهمة تنتظر semaphore بسعة أربعة. هل أصبحت العملية كلها محدودة؟

اعرض الحل المشروح

يقيد semaphore العمل النشط فقط. قد تظل مجموعة المهام المنتظرة تحتفظ بالمدخلات والإغلاقات البرمجية والذاكرة دون سياسة قبول مفيدة. استخدم قبولا محدودا بنمط منتج ومستهلك، أو نموذج عامل خضع للمراجعة، ومرر الإلغاء، وراقب كل مهمة بدأت. اختر الحدود من متطلبات الحمل بدلا من اعتبار أربعة رقما آمنا للجميع.

2. لماذا ينبغي لعملية خلفية لازمة ألا تحتفظ بـHttpContext بعد عودة الطلب؟

اعرض الحل المشروح

ينتمي السياق إلى الطلب الذي انتهى، وقد يحتوي حالة ذات نطاق أو ميزات متغيرة أو بيانات هوية ومدخلات حساسة انتهت مدة حياتها. انسخ البيانات الآمنة اللازمة فقط إلى عقد وظيفة صريح ودائم ومحدود، وأثبت الصلاحية عند الحد الصحيح، وامنح مالك الخلفية نطاقاته وإلغاءه ومعالجة فشله. مدة حياة الطلب ليست مدة حياة الوظيفة.

أهم ما تعلمته

  • انتظر كل مهمة تملكها، وأبق الموارد المستعارة حية إلى أن يكتمل العمل.
  • الإلغاء تعاوني، ولا تحد قيود التزامن الطوابير تلقائيا.
  • ديمومة الخلفية ومنع تكرار الأثر عقدان في التطبيق، لا خاصيتان توفرهما صياغة async وحدها.

المراجع الرسمية