لماذا نستخدم Generic Host و BackgroundService في تطبيق سطح المكتب

· آخر تحديث: · · C#, .NET, Generic Host, BackgroundService, WPF, WinForms, تطوير Windows, تصميم

سجل التعديلات (3 تحديثات، آخر تحديث 3 Sep، 2026)

سجل بالتغييرات التي أُجريت على هذا المقال. وحيثما حُفظت نسخة سابقة، تبقى متاحة للقراءة عبر رابط دائم يحمل معرّف DOI.

أُضيفت روابط الاستشارة الموجودة في الأصل الياباني (consultation_services). ولم يتغيّر نصّ المقالة نفسه. قراءة النسخة السابقة لهذا التحديث (DOI: 10.5281/zenodo.22240815)
أُعيدَت الترجمة العربية كترجمة كاملة عن النص الياباني الأصلي، وأُضيفَت خريطة المعرفة.
أعيدت الترجمة كترجمة كاملة عن النص الياباني الأصلي. كانت النسخة العربية السابقة مختصراً يسقط أبواباً وجداول ورسوم Mermaid وتعليقات الأشكال وFAQ. أُعيدت هذه العناصر وفق الأصل الياباني، والادّعاءات التقنية مطابقة للنسخة اليابانية.
النشر الأول
الاستشهاد بهذا المقال(DOI: 10.5281/zenodo.21621341)

هذا المقال محفوظ على Zenodo. يرد أدناه معرّف DOI الذي يشير دائمًا إلى أحدث نسخة، ومعرّف DOI المثبَّت على النسخة التي تقرؤها.

小村 豪 (2026). لماذا نستخدم Generic Host و BackgroundService في تطبيق سطح المكتب. شركة كومورا سوفت ذ.م.م.. https://doi.org/10.5281/zenodo.21621341 https://comcomponent.com/ar/blog/2026/03/12/002-generic-host-backgroundservice-desktop-app/

DOI (أحدث نسخة)
10.5281/zenodo.21621341
DOI (هذه النسخة)
10.5281/zenodo.22279737

عندما تنمو أداة Windows أو تطبيق مقيم قليلاً، تزداد بالتدريج المعالجات الواقعة خارج الواجهة. استطلاع دوريّ، ومراقبة ملفّات، وإعادة اتّصال، ومعالجة طابور، وتهيئة عند البدء، و flush عند الإنهاء. في البداية يكفي التجاوز بـ Form_Load أو OnStartup أو Task.Run، لكن إن نما الأمر كما هو صار مبهماً من يبدأ ومن يوقف ومن ينظر إلى الاستثناءات.

هذا موضع يفضَّل فيه تقرير من يملك عمر المعالجة قبل طريقة كتابة async / await نفسها. هنا ينفع Generic Host و BackgroundService في .NET.

جانب UI thread من async / await يتّصل بـ تنظيم async و UI thread في WPF/WinForms في صفحة واحدة و جدول قرار عمليّ لـ C# async/await: Task.Run و ConfigureAwait. هذه المرّة نضيّق على تنظيم بدء التطبيق كلّه وإيقافه، الطبقة الأبعد من ذلك.

ما يميل إلى التفكّك تدريجيّاً في العمل اليوميّ هو في الغالب هذا النطاق.

  • ظهور Task.Run من مواضع متفرّقة في النماذج و ViewModel
  • تبعثر شرط إيقاف الحلقة المقيمة بأعلام bool
  • بقاء معالجة تعمل عند الإنهاء، فلا يُغلق التطبيق أحياناً
  • صيرورة مداخل السجلات / الإعدادات / DI منفصلة لكلّ تقنيّة
  • الرغبة في الإنهاء بـ Environment.Exit، فيقفز finally

تفترض هذه المقالة أساساً تطبيقات WPF / WinForms / تطبيقات Windows مقيمة على .NET 6 وما بعده، وتنظّم لماذا ينفع Generic Host / BackgroundService بهدوء، وإلى أيّ حدّ يستحقّ الإدخال، وأين يصير التهاون مؤثّراً لاحقاً.

القارئ المتصوَّر ليس «هل تعرف BackgroundService أم لا» بقدر ما هو من يحار في موضع المعالجة المقيمة وطريقة حمل عمرها. نثبّت المصطلحات من الفصل التالي ليُقرأ حتّى من يرى الاسم أوّل مرّة، ومن يستخدمه أصلاً يمكنه البدء من جدول القرار في 2.2 وتقسيم الفصل 6.

الشكل الذي يتفكّك بالتدريجتبعثر Task.Run، وشروط الإيقاف المتفرّقة بأعلام bool، ومعالجة الإنهاء التي لا تُغلق، والمداخل المنفصلة لكلّ تقنيّة، كلّها تعود إلى نقطة واحدة: إبهام من يبدأ ومن يوقف ومن ينظر إلى الاستثناءات.Task.Run المتبعثرمالك العمر مبهمشرط إيقاف بأعلام boolمعالجة إنهاء لا تُغلقمدخل منفصل لكلّ تقنيّةتقرير من يملك العمر أوّلاً

الشكل 1: أشكال التفكّك متعدّدة، والجذر أنّ «من يملك عمر المعالجة» غير مقرَّر.

الشيفرة الواردة في هذه المقالة منشورة على GitHub كمجموعة عيّنات قابلة للبناء والتشغيل (مكتبة، وعرض console يمرّ من التشغيل إلى الإيقاف المنظّم، واختبارات وحدات).

generic-host-backgroundservice-desktop-app - komurasoft-blog-samples (GitHub)

تثبيت المصطلحات أوّلاً

هذا النوع من الحديث يصير صعب القراءة فجأة إن بقيت معاني الكلمات فضفاضة. لذا نثبّت الكلمات المستخدمة في هذه المقالة أوّلاً بإجمال.

  • Generic Host
    • أساس يجمع «البدء» و«الاعتماديّات» و«الإعدادات» و«السجلات» و«الإيقاف» في تطبيق .NET.
    • ليس آليّة خاصّة بـ ASP.NET Core؛ يُستخدم أيضاً في console و worker وتطبيقات سطح المكتب.
  • Host / IHost
    • الكيان بعد البناء.
    • يُشغَّل بـ StartAsync ويُوقف بـ StopAsync.
  • Hosted Service
    • معالجة مقيمة تُبدأ وتُوقف معلَّقة بعمر الـ host.
    • تُكتب بتنفيذ IHostedService، أو عادةً بالوراثة من BackgroundService.
  • BackgroundService
    • تنفيذ مساعد يسهّل كتابة IHostedService.
    • يمكن كتابة الجسم طويل العمر في ExecuteAsync، فيسهل تنظيم حلقات المراقبة والمعالجة الدوريّة.
  • lifetime
    • في هذه المقالة يعني «متى تبدأ تلك المعالجة، ومتى تنتهي، ومن يملك مسؤوليّة إيقافها».
    • أكثر من مدّة البقاء؛ إدارة عمر تشمل مسؤوليّة البدء ومسؤوليّة الإيقاف.
  • graceful shutdown
    • الإنهاء بعد إرسال إشارة التوقّف وترتيب المعالجة الجارية قدر الإمكان، لا الإنهاء القسريّ.
    • يدخل هنا مثلاً «عدم بدء الدورة التالية» و«تقرير إلى أيّ حدّ يُصرَّف الطابور» و«انتظار close أو flush».
  • DI
    • اختصار Dependency Injection؛ طريقة لاستلام تجميع كائنات الاعتماديّة عبر حاوية بدل كتابتها مباشرةً في جهة الاستدعاء.
    • في هذه المقالة يكفي فهم «عدم عمل new للـ logger والإعدادات والـ reader في كلّ موضع، وتكوينها مجتمعة عند المدخل».

هذا الحديث لا يقف عند «تقديم صنف مريح اسمه BackgroundService». يسهل تتبّعه إن قُرئ بوصفه حديث جمع بدء التطبيق وإيقافه في الـ host، وحمل lifetime المعالجة المقيمة كتصميم.

علاقة الكلمات المستخدمة في هذه المقالةGeneric Host أساس البدء والاعتماديّات والإعدادات والسجلات والإيقاف، والكيان بعد البناء هو IHost، وما يُعلَّق بعمره هو Hosted Service، و BackgroundService تنفيذ مساعد يسهّل كتابته.Generic Host (الأساس)IHost (الكيان بعد البناء)Hosted ServiceBackgroundServiceالجسم طويل العمر في ExecuteAsynclifetime = مسؤوليّة البدء والإيقاف

الشكل 2: تراتب المصطلحات. Hosted Service يُعلَّق بعمر الـ host، و BackgroundService تنفيذ مساعد له.

في المخطّط، يشير الخطّ المتّصل إلى علاقة قائمة دائماً، ويشير الخطّ المتقطّع إلى علاقة مشروطة (شروط قيامها مذكورة في شرح كلّ علاقة في الصفحة التفصيليّة). القائمة الكاملة للعلاقات (المجموع 17، مع الأدلّة ودرجة اليقين) وتعريفات المفاهيم الرئيسة مجمّعة في صفحة تفاصيل خريطة المعرفة (باليابانية). البيانات: JSON-LD / Turtle

المحتويات

  1. الخلاصة أوّلاً (بجملة)
  2. التنظيم في صفحة واحدة أوّلاً
    • 2.1. الصورة الإجماليّة
    • 2.2. جدول قرار الموضع
  3. لماذا ينفع في تطبيق سطح المكتب
    • 3.1. يسهل فصل مسؤوليّة الواجهة عن المعالجة المقيمة
    • 3.2. يمكن جمع مداخل البدء والإيقاف والاستثناءات في موضع واحد
    • 3.3. يسهل إدخال الإيقاف المنظّم في التصميم
    • 3.4. DI / السجلات / الإعدادات تكتمل من البداية
  4. الحالات التي تلائمه
  5. مثال تكوين أدنى (مثال WPF)
  6. تقسيم StartAsync / ExecuteAsync / StopAsync
    • 6.1. StartAsync
    • 6.2. ExecuteAsync
    • 6.3. StopAsync
    • 6.4. ملاحظة لـ .NET 10 وما بعده
  7. أنماط مضادّة شائعة
  8. قائمة فحص عند المراجعة
  9. دليل اختيار تقريبيّ
  10. الخلاصة
  11. المراجع

1. الخلاصة أوّلاً (بجملة)

  • Generic Host خيار قويّ جدّاً في تطبيق سطح المكتب أيضاً كـ أساس لإدارة البدء و lifetime.
  • BackgroundService وعاء لوضع «المعالجة طويلة العمر» على عمر مُدار بدل إطلاقها بـ Task.Run وتركها.
  • أكثر ما ينفع في العمل اليوميّ هو جمع مسؤوليّة البدء / مسؤوليّة الإيقاف / مراقبة الاستثناءات / السجلات / DI / الإعدادات في تصميم موضع واحد.
  • جعل StartAsync قصيراً، والجسم طويل العمر في ExecuteAsync، وترتيب الإنهاء في StopAsync يحسّن القراءة كثيراً.
  • التطبيقات المقيمة وتطبيقات الصينيّة ومراقبة الأجهزة والمزامنة الدوريّة والمعالجة اللاحقة المرتبة وحلقات إعادة الاتّصال ملائمة خصوصاً.
  • بالمقابل، وضع كلّ معالجة تتحرّك مرّة واحدة عند الضغط على زرّ في BackgroundService يصير مبالغاً فيه قليلاً.
  • StopAsync مفيد، لكنّه ليس تأميناً ضدّ انهيار العمليّة أو الإنهاء القسريّ. عدم تكديس أعمال الترتيب فيه أكثر من اللازم مهمّ أيضاً.

بمعنى آخر، نفع Generic Host / BackgroundService في تطبيق سطح المكتب ليس لأنّ «ثمّة معالجة خلفيّة»، بل لأنّكم تريدون حمل عمر تلك المعالجة الخلفيّة كتصميم، لا كملحق للواجهة.

ما يُجمع في تصميم موضع واحدالعناصر التي تميل إلى التبعثر — مسؤوليّة البدء ومسؤوليّة الإيقاف ومراقبة الاستثناءات والسجلات و DI والإعدادات — يمكن جمعها في تصميم موضع واحد، وهذا أكثر ما ينفع في العمل اليوميّ.مسؤوليّة البدءالجمع في تصميم موضع واحدمسؤوليّة الإيقافمراقبة الاستثناءاتالسجلات و DI والإعداداتالوضع على عمر مُدار

الشكل 3: قيمة BackgroundService في اجتماع المسؤوليّات المتبعثرة في تصميم موضع واحد.

2. التنظيم في صفحة واحدة أوّلاً

2.1. الصورة الإجماليّة

النظر أوّلاً إلى هذا المخطّط يسرّع الحديث كثيراً.

تشغيل تطبيق سطح المكتب (WPF / WinForms)Build / StartAsync للـ Hostتجهيز DI / Logging / ConfigurationHostedService.StartAsyncBackgroundService.ExecuteAsyncPeriodicTimer / طابور / إعادة اتّصال / حلقة مراقبةعرض MainWindow / MainFormتحديث الحالة / السجلات / I/O خارجيّالواجهة تستخدم Dispatcher / Invoke في المواضع اللازمة فقطإنهاء المستخدم / خطأ قاتل / StopApplicationIHost.StopAsyncإشعار CancellationTokenHostedService.StopAsyncclose للاتّصال / flush / إيقاف منظّم

الشكل 4: الصورة الإجماليّة من تشغيل الـ host إلى عرض الواجهة والحلقة المقيمة وإشارة التوقّف والإيقاف المنظّم.

لمن لا تظهر لديهم الرسوم، نرتّب التدفّق نفسه نصّاً أيضاً.

  1. تشغيل التطبيق (App.OnStartup في WPF، Main في WinForms)
  2. تسجيل الخدمات بـ Host.CreateApplicationBuilder ثمّ Build
  3. تشغيل الـ host بـ IHost.StartAsync (هنا تُثبَّت DI / السجلات / التكوين)
  4. استدعاء HostedService.StartAsync للخدمات المسجّلة
  5. انطلاق BackgroundService.ExecuteAsync (جسم حلقة المراقبة و PeriodicTimer ومعالجة الطابور وغيرها)
  6. عرض الواجهة (MainWindow / MainForm). يحدّث الـ worker مخزن الحالة أو السجلات، وتقرأ الواجهة ذلك في سياقها
  7. عند إنهاء المستخدم أو خطأ قاتل يُستدعى IHostApplicationLifetime.StopApplication، ويُنتقل إلى IHost.StopAsync
  8. يُشعَر بالإيقاف كـ CancellationToken (stoppingToken)، فتخرج حلقة ExecuteAsync
  9. في HostedService.StopAsync يُغلق الاتّصال ويُعمل flush للسجلات ثمّ الإنهاء

الشائع في تطبيقات الواجهة أن تتفرّق المسؤوليّات بالتدريج بين Program.cs / App.xaml.cs / Form_Load / Closing / Task.Run / Timer / static singleton.

بإدخال Host يمكن التقسيم تقريباً كالتالي.

  • الواجهة: الشاشة، الإدخال، العرض
  • HostedService / BackgroundService: المعالجة المقيمة، المراقبة، معالجة الطابور، المعالجة الدوريّة
  • خدمات DI: منطق الأعمال الفعليّ، الاتّصال الخارجيّ، الإعدادات، السجلات

هذا القطع وحده يغيّر سهولة المراجعة كثيراً.

التقسيم الثلاثيّ بعد إدخال الـ hostالواجهة للشاشات والإدخال والعرض، و HostedService و BackgroundService للمعالجة المقيمة والمراقبة ومعالجة الطابور والمعالجة الدوريّة، وخدمات DI لمنطق الأعمال والاتّصال الخارجيّ والإعدادات والسجلات.تطبيق سطح المكتبالواجهة: شاشة وإدخال وعرضHostedService: إقامة ومراقبةخدمات DI: منطق الأعمالمعالجة الطابور والدوريّة هنا أيضاً

الشكل 5: من شكل المسؤوليّات المتبعثرة إلى تقسيم ثلاثيّ: الواجهة والمعالجة المقيمة والمعالجة الفعليّة.

2.2. جدول قرار الموضع

المراد المرشّح الأوّل للموضع السبب
تهيئة خفيفة فور التشغيل StartAsync معناها واضح كمعالجة قصيرة تشارك في البدء
مراقبة / استطلاع / إعادة اتّصال طويلة العمر ExecuteAsync يسهل تشغيلها مع عمر الخدمة
إشارة التوقّف / flush / close عند الإنهاء StopAsync يسهل كتابة الإيقاف المنظّم مع CancellationToken
تكوين الاعتماديّات والإعدادات والسجلات Host.CreateApplicationBuilder يمكن جمع المدخل في موضع واحد
تحديث الشاشة جانب الواجهة لمس الواجهة مباشرةً من الـ worker أقلّ حوادث
معالجة مرّة واحدة لكلّ ضغط زرّ ميثود async عاديّة غالباً لا حاجة إلى HostedService
معالجة لاحقة خلفيّة مرتّبة Channel<T> + BackgroundService أسهل في إدارة العمر والحدّ من الإطلاق والترك

قيمة إدخال Host ليست في «جعل شيء غير متزامن»، بل في وضوح قرار أين يُوضع.

3. لماذا ينفع في تطبيق سطح المكتب

3.1. يسهل فصل مسؤوليّة الواجهة عن المعالجة المقيمة

يبدو تطبيق سطح المكتب كأنّ الواجهة بطله، لكن ما يثقل في العمل اليوميّ هو في الغالب خارج الواجهة.

مثلاً:

  • مزامنة حالة كلّ 10 ثوانٍ
  • إعادة الاتّصال بجهاز أو خادم
  • مراقبة ملفّات والاستيعاب
  • معالجة لاحقة مكدّسة في طابور
  • نقل سجلات أو إرسال مقاييس
  • تسخين الذاكرة المؤقّتة عند التشغيل

هذه ليست «أحداث شاشة» بل معالجات معلَّقة بعمر التطبيق كلّه.

إن أسكنتموها في code-behind للنموذج أو النافذة، تبدأ مسؤوليّة الإيقاف عند إغلاق الشاشة، ومسؤوليّة التقاط الاستثناءات، ومسؤوليّة تقرير إعادة المحاولة أو backoff، بالاختلاط بظروف الواجهة.

باستخدام BackgroundService يظهر في شكل الشيفرة إعلان أنّ «هذه المعالجة تعيش طوال عمل التطبيق». هذا قويّ بهدوء.

مقابلة مواضع إسكان المعالجة المقيمةإسكان المعالجة المقيمة في code-behind يخلط مسؤوليّة الإيقاف والاستثناءات وإعادة المحاولة بظروف الواجهة، أمّا وضعها على BackgroundService فيُخرج إعلان العيش طوال العمل في شكل الشيفرة.code-behindBackgroundServiceأين تُسكن المعالجة المقيمةالإيقاف والاستثناءات وإعادة المحاولة تختلط بالواجهةإعلان العيش طوال العمل يظهر في الشكل

الشكل 6: المعالجة نفسها، وطريقة الإسكان تغيّر اختلاط المسؤوليّات تماماً.

3.2. يمكن جمع مداخل البدء والإيقاف والاستثناءات في موضع واحد

حتّى في تطبيق سطح مكتب بلا Host، يمكن عمل شيء مشابه بترتيب ServiceCollection و ConfigurationBuilder و LoggerFactory كلّ على حدة.

غير أنّ ذلك الشكل يتفرّق بالتدريج في الغالب.

  • DI في Program.cs
  • الإعدادات static خاصّ
  • السجلات factory منفصل
  • معالجة الإنهاء في ApplicationExit
  • المعالجة المقيمة في Task.Run

بهذا الشكل يعمل الأمر في البداية. لكن عند المراجعة بعد أشهر يصير من يملك عمر التطبيق صعب الرؤية.

باستخدام Generic Host تدخل في الإطار نفسه:

  • تسجيل الخدمات
  • قراءة التكوين
  • تكوين السجلات
  • تشغيل hosted service
  • إشعار الإيقاف
  • الإيقاف الكلّيّ بـ IHostApplicationLifetime

أي يسهل جمع مدخل «كيف يبدأ هذا التطبيق وكيف يتوقّف» في موضع واحد. في التطبيقات المقيمة ينفع هذا لاحقاً.

جمع المداخل المتفرّقة في الـ hostمن شكل تتفرّق فيه DI والإعدادات والسجلات ومعالجة الإنهاء والمعالجة المقيمة في مواضع مختلفة، إلى شكل يدخل فيه تسجيل الخدمات وقراءة التكوين وتكوين السجلات وتشغيل hosted service وإشعار الإيقاف والإيقاف الكلّيّ في الإطار نفسه.المداخل تتفرّق حسب التقنيّةيصير مالك العمر غير واضحالجمع في Generic Hostمدخل البدء والإيقاف في موضع واحدالتسجيل والتكوين والسجلات وإشعار الإيقاف

الشكل 7: الترتيب المنفصل يعمل أيضاً، لكنّ الدخول في الإطار نفسه هو ما ينفع بعد أشهر.

3.3. يسهل إدخال الإيقاف المنظّم في التصميم

المعالجة المقيمة أصعب في الإيقاف منها في البدء. البدء قد يُكتب في 3 أسطر، أمّا الإنهاء فيزيد ما يُفكَّر فيه دفعة واحدة.

مثلاً عند الإنهاء:

  • إلغاء I/O الجاري
  • عدم بدء الدورة التالية
  • تقرير إلى أيّ حدّ تُصرَّف بقايا الطابور
  • إغلاق مقابس أو كائنات COM
  • انتظار flush السجلات أو حفظ الحالة

جمع هذا في FormClosing يختلط بظروف الشاشة فيصير شاقاً.

مع Host / BackgroundService يوجد CancellationToken و StopAsync، فـ مسار التوقّف موجود من البداية.

ليس سحراً طبعاً. عند الانهيار أو kill قد لا يُستدعى StopAsync. ومع ذلك، وجود تصميم «عند الإنهاء الطبيعيّ نتوقّف بهذا المسار» وحده يهدّئ الأمر كثيراً.

مسار التوقّفعند الإنهاء يلزم إلغاء I/O الجاري وإيقاف الدورة التالية ومعاملة بقايا الطابور وانتظار close أو flush، ووجود مسار توقّف من البداية عبر CancellationToken و StopAsync هو ما ينفع.إشارة التوقّفإشعار CancellationTokenعدم بدء الدورة التاليةإلغاء I/O الجاريclose و flush في StopAsyncلا يمرّ عند الانهيار أو kill

الشكل 8: المعالجة المقيمة أصعب في الإيقاف، ولهذا ينفع وجود مسار توقّف من البداية.

3.4. DI / السجلات / الإعدادات تكتمل من البداية

حسن Generic Host ليس BackgroundService وحده.

  • Host.CreateApplicationBuilder يكتمل به أساس DI / التكوين / السجلات
  • يسهل استخدام appsettings.json ومتغيّرات البيئة كما هي
  • يمكن للواجهة والـ worker استخدام ILogger<T> بالأسلوب نفسه
  • يمكن جمع الإعدادات بسلسلة IOptions<T> عند الحاجة

خصوصاً في مشاريع أدوات Windows، شائع جدّاً أنّ «الإعدادات والـ logger المحمولين بتهاون في static لأنّ المشروع كان صغيراً في البداية يصيران شاقّين لاحقاً».

وضع هذا على الـ host من البداية يقلّل انقطاع النفس عندما يسمن التطبيق قليلاً.

4. الحالات التي تلائمه

Generic Host / BackgroundService ينفعان خصوصاً في حالات كهذه.

  • تطبيق مقيم في الصينيّة ثمّة مزامنة دوريّة ومراقبة وإشعارات وإعادة اتّصال
  • تطبيق اتّصال أجهزة / كاميرات / مقابس ثمّة إبقاء الاتّصال ومراقبة وإعادة محاولة وجلب حالة
  • أداة ربط ملفّات ثمّة مراقبة وطابور استيعاب ومعالجة مرتّبة
  • منع تضخّم أداة داخليّة صغيرة في البداية، لكن الإعدادات والسجلات و I/O الخارجيّ مرشّحة للزيادة
  • تطبيق جودة إنهائه مهمّة لا يُراد ترك حالة ناقصة عند الإغلاق

بالمقابل، ثمّة حالات لا يلزم إدخال الـ host فوراً.

  • أداة صغيرة تُشغَّل مرّة وتعالج مرّة وتنتهي
  • شاشة تكتمل بأحداث الواجهة تقريباً بلا معالجة خلفيّة
  • أداة مساعدة داخليّة صغيرة حقّاً لا تكاد تزيد فيها الاعتماديّات أو الإعدادات

الـ Host ليس «إلزاماً». غير أنّه حين تظهر معالجتان مقيمتان أو أكثر، يمكن النظر فيه بإيجابيّة كبيرة. أرخص بكثير من ترتيب Task.Run المتبعثر لاحقاً.

معيار إدخال الـ hostإن ظهرت معالجتان مقيمتان أو أكثر يُنظر في الـ host بإيجابيّة، أمّا الأداة الصغيرة ذات التشغيل الواحد أو الشاشة التي تكتمل بأحداث الواجهة فلا يلزم إدخالها فوراً.نعملاهل تظهر معالجتان مقيمتان أو أكثرالنظر في الـ host بإيجابيّةلا يلزم الإدخال فوراًأرخص من ترتيب Task.Run لاحقاً

الشكل 9: الـ host ليس إلزاماً، لكنّ إدخاله عندما تبدأ المعالجة المقيمة بالزيادة أرخص من الترتيب اللاحق.

5. مثال تكوين أدنى (مثال WPF)

كمثال، نكتب تكويناً أدنى يشغّل host في WPF ويحرّك BackgroundService يقرأ حالة خارجيّة كلّ 5 ثوانٍ. في WinForms الفكرة شبه نفسها، ويتغيّر المدخل فقط إلى Main / ApplicationContext.

الشيفرة ستظهر في ثلاثة أجزاء، لذا نرتّب تكوين الملفّات أوّلاً.

الملفّ المحتوى الموضع
App.xaml.cs توليد الـ host، تسجيل DI، StartAsync / StopAsync، عرض MainWindow 5.1
DevicePollingBackgroundService.cs حلقة مقيمة تقرأ الحالة كلّ 5 ثوانٍ 5.2
StatusStore.cs حالة يشاركها الـ worker والواجهة. سجلّ DeviceStatus موضوع هنا أيضاً 5.3
IDeviceStatusReader.cs / DeviceStatusReader.cs المعالجة التي تقرأ الحالة فعلاً من الخارج محذوفة من النصّ. التنفيذ في عيّنة GitHub
MainWindow.xaml / MainWindow.xaml.cs الشاشة. تقرأ StatusStore وتعرض محذوفة من النصّ. شيفرة شاشة WPF عاديّة

عيّنة GitHub هي هذا التكوين بصيغة يمكن تشغيلها في console، وتتضمّن تنفيذ BackgroundService و StatusStore، وعرضاً يمرّ من التشغيل إلى الإيقاف المنظّم، واختبارات وحدات.

5.1. App.xaml.cs

using System.Windows;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace DesktopHostSample;

public partial class App : Application
{
    private IHost? _host;

    protected override async void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);

        HostApplicationBuilder builder = Host.CreateApplicationBuilder(e.Args);

        builder.Services.Configure<HostOptions>(options =>
        {
            options.ShutdownTimeout = TimeSpan.FromSeconds(15);
        });

        builder.Services.AddSingleton<MainWindow>();
        builder.Services.AddSingleton<StatusStore>();
        builder.Services.AddScoped<IDeviceStatusReader, DeviceStatusReader>();
        builder.Services.AddHostedService<DevicePollingBackgroundService>();

        _host = builder.Build();

        await _host.StartAsync();

        MainWindow mainWindow = _host.Services.GetRequiredService<MainWindow>();
        mainWindow.Show();
    }

    protected override async void OnExit(ExitEventArgs e)
    {
        if (_host is not null)
        {
            await _host.StopAsync();
            _host.Dispose();
        }

        base.OnExit(e);
    }
}

نقاط هذا الشكل ثلاث.

  1. تشغيل الـ host قبل عرض الواجهة
  2. await صريح لـ StopAsync عند الإنهاء
  3. جمع DI / hosted service / مهلة الإيقاف عند المدخل

ShutdownTimeout هو الحدّ الأعلى الافتراضيّ الذي ينتظره IHost.StopAsync لمعالجة الإنهاء. القيمة الافتراضيّة تختلف حسب الإصدار: 5 ثوانٍ في .NET 6، و 30 ثانية في .NET 7 وما بعده. كتابة 15 ثانية هنا هي من أجل تقرير الحدّ الأعلى بأنفسكم وفق أبطأ معالجة إنهاء. المعيار تقريباً «مهلة I/O الجاري + الوقت اللازم لـ close / flush» مع هامش صغير. إن قصُر قُطع flush في منتصفه، وإن طال بدا التطبيق «لا يُغلق»، لذا يقلّ الحادث إن قُرّر مرّة بدل ترك الافتراضيّ كما هو.

طريقة تقرير ShutdownTimeoutحدّ انتظار معالجة الإنهاء لا يُترك على الافتراضيّ، بل يُقرَّر بأنفسكم وفق أبطأ معالجة إنهاء. إن قصُر قُطع flush في منتصفه، وإن طال بدا التطبيق لا يُغلق.معرفة أبطأ معالجة إنهاءإضافة حصّة flush إلى مهلة I/Oتقرير الحدّ الأعلى وضبطهأقصر من اللازم: يُقطع flushأطول من اللازم: يبدو أنّه لا يُغلق

الشكل 10: لا تتركوا ShutdownTimeout على الافتراضيّ؛ احسبوه عكسياً من أبطأ معالجة إنهاء.

جعل OnExit نفسه async يحتاج بعض العناية بسبب إطار الواجهة، لكن معنى كتابة تدفّق «إيقاف الـ host عند الإنهاء» بوضوح كبير.

5.2. BackgroundService

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace DesktopHostSample;

public sealed class DevicePollingBackgroundService(
    IServiceScopeFactory scopeFactory,
    StatusStore statusStore,
    ILogger<DevicePollingBackgroundService> logger) : BackgroundService
{
    public override async Task StartAsync(CancellationToken cancellationToken)
    {
        logger.LogInformation("Device polling service is starting.");
        await base.StartAsync(cancellationToken);
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        logger.LogInformation("Device polling loop started.");

        using var timer = new PeriodicTimer(TimeSpan.FromSeconds(5));

        while (await timer.WaitForNextTickAsync(stoppingToken))
        {
            try
            {
                using IServiceScope scope = scopeFactory.CreateScope();
                IDeviceStatusReader reader =
                    scope.ServiceProvider.GetRequiredService<IDeviceStatusReader>();

                DeviceStatus status = await reader.ReadAsync(stoppingToken);
                statusStore.Update(status);
            }
            catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
            {
                break;
            }
            catch (Exception ex)
            {
                logger.LogError(ex, "Device polling failed.");
            }
        }

        logger.LogInformation("Device polling loop finished.");
    }

    public override async Task StopAsync(CancellationToken cancellationToken)
    {
        logger.LogInformation("Device polling service is stopping.");
        await base.StopAsync(cancellationToken);
        logger.LogInformation("Device polling service stopped.");
    }
}

المهمّ هنا كتابة ExecuteAsync بصراحة كـ حلقة while مُدارة.

  • الدورة بـ PeriodicTimer
  • الإيقاف بـ stoppingToken
  • الاستثناءات بالسجلات
  • إن لزمت اعتماديّة scoped يُقطع scope في كلّ مرّة

بهذا الشكل يصير «أين تبدأ هذه المعالجة المقيمة الآن، وأين تتوقّف، وأين يُرى الفشل» أسهل قراءة بكثير.

شكل حلقة while المُدارةانتظار الدورة بـ PeriodicTimer، وقطع scope في كلّ مرّة لاعتماديّة scoped والقراءة، وتحديث مخزن الحالة، وإبقاء الاستثناءات في السجلّ ومتابعة الحلقة، والخروج بالإلغاء عبر stoppingToken.فشلstoppingTokenانتظار دورة PeriodicTimerقطع scope وجلب الاعتماديّةقراءة الحالة وتحديث المخزنإبقاء الاستثناء في السجلّ والمتابعةالخروج من الحلقة والإنهاء

الشكل 11: ExecuteAsync «حلقة while مُدارة». معاملة الدورة والإيقاف والاستثناءات و scope تُقرأ في موضع واحد.

5.3. لا تجعلوا مشاركة الحالة متّصلة بالواجهة مباشرةً

لمس كائنات الواجهة مباشرةً من الـ worker يعيد مشكلة UI thread في النهاية.

لذا الأأمن أوّلاً:

  • الـ worker يحدّث مخزن حالة أو طبقة رسائل
  • الواجهة تقرأ تلك الحالة / تطبّقها في سياقها

يمكن جعل StatusStore طبقة مشاركة رقيقة كهذه مثلاً.

namespace DesktopHostSample;

public sealed class StatusStore
{
    private readonly object _gate = new();
    private DeviceStatus _current = DeviceStatus.Empty;

    public DeviceStatus Current
    {
        get
        {
            lock (_gate)
            {
                return _current;
            }
        }
    }

    public void Update(DeviceStatus next)
    {
        lock (_gate)
        {
            _current = next;
        }
    }
}

public sealed record DeviceStatus(string Message)
{
    public static readonly DeviceStatus Empty = new("No Data");
}

إن لزم إشعار فوريّ للواجهة، استخدموا Dispatcher / BeginInvoke / أحداثاً / messenger وغيرها. غير أنّ تلك المسؤوليّة أبعد عن الاختلاط إن حُملت عند حدّ الواجهة.

فصل يمنع اتّصال مشاركة الحالة بالواجهة مباشرةًالـ worker لا يلمس كائنات الواجهة مباشرةً بل يحدّث مخزن حالة أو طبقة رسائل، والواجهة تقرأ تلك الحالة في سياقها وتطبّقها. مسؤوليّة الإشعار الفوريّ عند حدّ الواجهة.worker (الحلقة المقيمة)تحديث مخزن الحالةالواجهةالقراءة في سياقهاالإشعار الفوريّ مسؤوليّة حدّ الواجهة

الشكل 12: وضع طبقة مشاركة رقيقة بين الـ worker والواجهة يمنع عودة مشكلة UI thread.

6. تقسيم StartAsync / ExecuteAsync / StopAsync

اختلاط هذه الثلاثة يعكّر ذهن القارئ سريعاً. التقسيم التالي مستقرّ إلى حدّ بعيد.

6.1. StartAsync

StartAsync موضع لوضع معالجة قصيرة تشارك في البدء.

ما يلائمه:

  • سجلّ التشغيل
  • بدء اشتراك خفيف
  • تجهيز حالة ابتدائيّة تنتهي سريعاً
  • ترتيب أدنى حول base.StartAsync

ما لا يلائمه:

  • تسخين يستغرق عشرات الثواني
  • حلقة لا نهائيّة
  • جسم معالجة يرتّب I/O ثقيلاً

تثقيل StartAsync يجعل نهوض التطبيق كلّه يبدو بطيئاً. اعتبار هذا موضع كتابة «إشارة البدء» يقلّل الحوادث.

تمييز ما يُوضع في StartAsyncالمعالجة القصيرة المشاركة في البدء مثل سجلّ التشغيل وبدء اشتراك خفيف تلائم StartAsync، أمّا التسخين الطويل والحلقة اللانهائيّة و I/O الثقيل فيجعل نهوض التطبيق كلّه يبدو بطيئاً.نعملاهل هي معالجة قصيرة تشارك في البدءوضعها في StartAsyncإلى الجانب الجسميّ مثل ExecuteAsyncالثقيل يبطئ نهوض التطبيق

الشكل 13: StartAsync موضع كتابة «إشارة البدء»، لا موضع المعالجة الثقيلة.

6.2. ExecuteAsync

ExecuteAsync هو جسم عمر الخدمة.

ما يلائمه:

  • الاستطلاع
  • حلقة المراقبة
  • حلقة إعادة الاتّصال
  • مستهلك يقرأ Channel<T>
  • معالجة دوريّة
  • المعالجات التي «تعيش حتى الإيقاف» عموماً

الحيل هنا ثلاث.

  1. تمرير CancellationToken من البداية إلى النهاية
  2. منع موت الحلقة كلّها بصمت بسبب استثناء
  3. عدم زيادة إعادة المحاولة أو backoff ارتجالاً أكثر من اللازم

BackgroundService مريح، لكن إن تُرك قد يصير أيضاً «حلقة عملاقة تبتلع كلّ شيء». اقتطاع المعالجة الفعليّة إلى خدمة أخرى، وجعل ExecuteAsync نفسه أقرب إلى إدارة العمر والتنسيق، أسهل قراءة.

حيل الإبقاء على ExecuteAsync جسماًتمرير CancellationToken من البداية إلى النهاية، ومنع موت الحلقة بصمت بسبب استثناء، وعدم زيادة إعادة المحاولة أو backoff ارتجالاً، واقتطاع المعالجة الفعليّة إلى خدمة أخرى.ExecuteAsyncتمرير الـ token حتى النهايةعدم الموت بصمتعدم زيادة إعادة المحاولة أكثر من اللازمالتقريب إلى إدارة العمر

الشكل 14: ثلاث حيل تُبقي ExecuteAsync موضع إدارة عمر لا حلقة عملاقة.

6.3. StopAsync

StopAsync موضع لـ الترتيب عند الإنهاء الطبيعيّ.

ما يلائمه:

  • سجلّ الإيقاف
  • إلغاء مؤقّت / اشتراك / مراقبة
  • ترتيب موارد تريدون close / flush صريحاً لها
  • انتظار الإنهاء عبر base.StopAsync

غير أنّ عدم انتظار كلّ شيء من StopAsync مهمّ أيضاً.

  • سقطت العمليّة
  • أُنهي قسريّاً
  • قُتل من جانب نظام التشغيل

في إنهاء من هذا النوع قد لا يمرّ أصلاً.

لذا مهمّ:

  • إنجاز الاستدامة قدر الإمكان صغيراً في الأوقات العاديّة
  • عدم جعل التصميم لا يتّسق إلا عند الإنهاء
  • جعل cleanup متماثلاً (idempotent)

محاولة إنقاذ العالم عند الإنهاء وحده تعكّر الأمر في الغالب.

النطاق الذي يجوز انتظاره من StopAsyncعند الإنهاء الطبيعيّ يمكن الترتيب في StopAsync، لكنّه قد لا يمرّ عند انهيار العمليّة أو الإنهاء القسريّ أو kill من نظام التشغيل، لذا تُنجَز الاستدامة صغيراً في الأوقات العاديّة ويُجعل cleanup متماثلاً.إنهاء طبيعيّانهيار أو killأيّ نوع إنهاءيمكن الترتيب في StopAsyncقد لا يمرّ StopAsyncالاستعداد باستدامة الأوقات العاديّةجعل cleanup متماثلاً

الشكل 15: StopAsync عون للإنهاء الطبيعيّ، لا تأمين ضدّ الإنهاء غير الطبيعيّ.

6.4. ملاحظة لـ .NET 10 وما بعده

كتغيير كاسر في .NET 10 (إصدار نوفمبر 2025)، تغيّر السلوك بحيث يُنفَّذ BackgroundService.ExecuteAsync كلّه كمهمّة خلفيّة.

سابقاً كان ثمّة سلوك أصعب فهماً قليلاً: الجزء المتزامن قبل أوّل await يحجب بدء الخدمات الأخرى عند التشغيل. بهذا التغيير قلّ احتمال حادث «الأسطر الأولى من ExecuteAsync كانت تثقل التشغيل». بالمقابل، إن كان الهدف .NET 9 أو أقدم، فالسلوك ما يزال الجانب الذي لم يتغيّر. تحقّقوا أوّلاً أيّ الجانبين مشروعكم.

ومع ذلك، من جهة التصميم أسهل قراءة الإبقاء على التقسيم:

  • معالجة قصيرة تشارك في البدء ← StartAsync
  • جسم طويل العمر ← ExecuteAsync

إن أردتم التحكّم بتوقيت التشغيل بدقّة أكبر، يدخل IHostedLifecycleService في الأفق. هذا جانب هادئ ينفع عندما يسمن التطبيق المقيم.

فرق سلوك ExecuteAsync حسب الإصدارفي .NET 9 وما قبله قد يحجب الجزء المتزامن قبل أوّل await بدء الخدمات الأخرى، ومن .NET 10 يُنفَّذ ExecuteAsync كلّه كمهمّة خلفيّة. في الحالين أسهل قراءة فصل المعالجة القصيرة عند البدء إلى StartAsync..NET 9 وما قبله.NET 10 وما بعدهما .NET المستهدف؟الجزء المتزامن قبل await قد يسدّ التشغيلالكلّ يُنفَّذ في الخلفيّةالمعالجة القصيرة عند البدء إلى StartAsync

الشكل 16: السلوك يتغيّر حسب الإصدار، أمّا تصميم فصل StartAsync و ExecuteAsync فلا يتغيّر.

7. أنماط مضادّة شائعة

7.1. بدء حلقة لا نهائيّة في Window_Loaded / Form_Shown

سهل في البداية. لكن مسؤوليّة الإيقاف ومسؤوليّة الاستثناءات تلتصق بجانب الواجهة.

عندما تبدأ شروط مثل «يتوقّف عند إغلاق الشاشة» «لا يتوقّف عند التصغير إلى الصينيّة» «يُعاد التشغيل عند تغيير الإعدادات» بالزيادة، يصير الأمر شاقاً سريعاً.

7.2. إطلاق Task.Run وتركه

Task.Run نفسه ليس سيّئاً. السيّئ هو غياب مالك للعمر والاستثناءات.

خصوصاً عند بدء معالجة مقيمة بـ Task.Run(async () => { while (...) { ... } }) يصير مبهماً:

  • متى تنتهي
  • من ينتظر
  • كيف تُرى الاستثناءات
  • إلى أيّ حدّ يُنتظر عند الإنهاء

وضع هذا على BackgroundService وحده يسهّل التنظيم كثيراً.

7.3. لمس الواجهة مباشرةً من BackgroundService

هذا لغم. مشكلة UI thread ومشكلة lifetime تختلطان دفعة واحدة.

الـ worker لا يعبث بالواجهة مباشرةً، والأأمن وضع حدّ عبر واحد من:

  • حالة
  • حدث
  • رسالة
  • طابور

7.4. جمع معالجة الحفظ المهمّة في StopAsync فقط

StopAsync يعين الإنهاء الطبيعيّ، لكنّه ليس الحكم الأخير.

تصميم لا يحفظ إلا عند الإنهاء، ولا يعمل flush إلا عند الإنهاء، ولا يتّسق إلا عند الإنهاء،

ينهار عند الانهيار.

7.5. الإسقاط بتهاون بـ Environment.Exit رغم استخدام الـ host

هذا شائع أيضاً.

استدعاء Environment.Exit بـ «مزعج، فلنُسقطه» يقطع بأنفسكم مسار الإيقاف المنظّم الذي يملكه الـ host.

إن أردتم إنهاء الكلّ عند خطأ قاتل، فالأوضح أوّلاً استخدام IHostApplicationLifetime.StopApplication() وتمرير المسار النظاميّ للتوقّف.

مسارا الإنهاء الكلّيّالإسقاط بـ Environment.Exit يقطع مسار الإيقاف المنظّم الذي يملكه الـ host، لذا عند إرادة إنهاء الكلّ بخطأ قاتل يُمرَّر المسار النظاميّ بـ IHostApplicationLifetime.StopApplication.Environment.ExitStopApplicationإرادة الإنهاء بخطأ قاتلبأيّهما يُسقَطقطع مسار الإيقاف المنظّمالمسار النظاميّ للتوقّفالإنهاء بعد المرور بـ StopAsync

الشكل 17: استخدام Environment.Exit رغم استخدام الـ host يعني قطع مسار التوقّف الذي جهّزتموه بأنفسكم.

8. قائمة فحص عند المراجعة

عند مراجعة تطبيق سطح مكتب يستخدم Generic Host / BackgroundService، النظر بالترتيب التالي أوضح.

  • هل تلك المعالجة معلَّقة بعمر التطبيق، أم مجرّد معالجة حدث واجهة
  • هل مسؤوليّة التشغيل مقسومة مناسبة بين StartAsync / ExecuteAsync / StopAsync
  • هل صار StartAsync أثقل من اللازم
  • هل يمرّر ExecuteAsync الـ CancellationToken حتى النهاية
  • هل لا تُمسَك اعتماديّة scoped مباشرةً من hosted service
  • هل لا يلمس الـ worker كائنات الواجهة مباشرةً
  • هل لا تُبتلع الاستثناءات بصمت
  • هل لم تصر حلقة إعادة المحاولة عالية التكرار بلا حدّ
  • هل لوقت الانتظار عند الإنهاء حدّ أعلى
  • هل لم يختلط إنهاء يفترض Environment.Exit أو قتل العمليّة

بهذه القائمة يصير الفرق بين «أدخلنا Host كيفما اتّفق» و «نُظِّم العمر كتصميم» أسهل رؤية بكثير.

9. دليل اختيار تقريبيّ

المراد ما يُختار أوّلاً
إكمال DI / السجلات / الإعدادات للتطبيق كلّه Host.CreateApplicationBuilder
تشغيل حلقة مقيمة BackgroundService
الدوران بفاصل ثابت PeriodicTimer + BackgroundService
تصريف معالجة لاحقة مرتّبة Channel<T> + BackgroundService
استخدام خدمة scoped IServiceScopeFactory.CreateScope()
إشعار الكلّ بالإنهاء الطبيعيّ IHostApplicationLifetime.StopApplication()
تحديث الواجهة Dispatcher / Invoke في جانب الواجهة
تشغيل شاشة مرّة واحدة ميثود async عاديّة
تحكّم أدقّ بدورة حياة التشغيل النظر في IHostedLifecycleService

10. الخلاصة

سبب إدخال Generic Host / BackgroundService إلى تطبيق سطح المكتب ليس «الرغبة في كتابة تشبه الويب».

ما ينفع حقّاً ثلاثة.

  1. جمع مسؤوليّة البدء والإيقاف في موضع واحد
  2. حمل عمر المعالجة طويلة العمر كتصميم
  3. معاملة الإيقاف المنظّم من المدخل لا كإضافة لاحقة

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

بالمقابل، الفصل إلى:

  • الواجهة واجهة
  • المعالجة المقيمة hosted service
  • المعالجة الفعليّة خدمة DI
  • الإنهاء StopAsync و CancellationToken

وحده يرتّب الأمر كثيراً.

تقسيم الخلاصةالفصل إلى الواجهة واجهة، والمعالجة المقيمة hosted service، والمعالجة الفعليّة خدمة DI، والإنهاء StopAsync و CancellationToken، يرتّب الأمر كثيراً.التطبيق كلّهالواجهة واجهةالمعالجة المقيمة hosted serviceالمعالجة الفعليّة خدمة DIالإنهاء StopAsync و token

الشكل 18: تقسيم بلا بريق، لكنّ ما يقلّل «يصير غريباً أحياناً عند الإغلاق» هو هذا التنظيم.

لا بريق فيه. غير أنّ تصميماً هادئاً كهذا ينفع بقوّة في العمل اليوميّ. يقلّل اللزوجة المزعجة من نوع «يصير غريباً أحياناً عند الإغلاق» و«لا يُعرف أين يُوقَف».

إن كنتم عالقين في أدوات Windows أو تطبيقات مقيمة عند التحويل إلى BackgroundService، أو تصميم البدء / الإيقاف، أو حلقات المراقبة، أو تنظيم عمر مراقبة COM / المقابس / الملفّات، أو فصل عيوب الإنهاء، فاستشيروا من مراجعة التصميم أو ترتيب الاتّجاه.

11. مراجع

أحدث المقالات التي تشترك في نفس الوسوم. عمّق فهمك بمواضيع مرتبطة.

ترتبط هذه المقالة بشكل طبيعي بصفحات الخدمات التالية.

الأسئلة الشائعة

أسئلة شائعة حول موضوع هذه المقالة.

هل يمكن استخدام Generic Host في تطبيق سطح مكتب أيضاً؟
نعم. Generic Host ليس آليّة خاصّة بـ ASP.NET Core؛ يمكن استخدامه في تطبيقات console و worker و WPF / WinForms كأساس يجمع البدء والاعتماديّات والإعدادات والسجلات والإيقاف. يلائم خصوصاً التطبيقات المقيمة وتطبيقات الصينيّة ومراقبة الأجهزة والمزامنة الدوريّة والمعالجة اللاحقة المرتبة وحلقات إعادة الاتّصال.
لماذا نستخدم BackgroundService؟
وعاء لوضع «المعالجة طويلة العمر» على عمر مُدار بدل إطلاقها بـ Task.Run وتركها. هو تنفيذ مساعد يسهّل كتابة IHostedService، ويمكن كتابة جسم حلقة المراقبة أو المعالجة الدوريّة في ExecuteAsync. يظهر في شكل الشيفرة إعلان أنّ «هذه المعالجة تعيش طوال عمل التطبيق»، فيُجمع مسؤوليّة البدء ومسؤوليّة الإيقاف ومراقبة الاستثناءات والسجلات و DI والإعدادات في تصميم موضع واحد.
كيف نوزّع StartAsync / ExecuteAsync / StopAsync؟
يصير أوضح إن وُضعت التهيئة القصيرة المشاركة في البدء في StartAsync، والجسم طويل العمر في ExecuteAsync، وإشارة التوقّف و flush / close عند الإنهاء في StopAsync. في المقابل، معالجة تتحرّك مرّة واحدة عند الضغط على زرّ تكفي لها ميثود async عاديّة؛ وضع كلّ شيء في BackgroundService يصير مبالغاً فيه.
هل يجوز جمع كلّ معالجة الإنهاء في StopAsync؟
لا يُستحسن. StopAsync مفيد، لكنّه ليس تأميناً ضدّ انهيار العمليّة أو الإنهاء القسريّ، لذا لا تُكدَّس أعمال الترتيب فيه أكثر من اللازم. يُصمَّم الإيقاف المنظّم (إلغاء I/O الجاري، إيقاف الدورة التالية، سياسة بقايا الطابور، إغلاق الاتّصال، flush السجلات) بـ CancellationToken و StopAsync، مع افتراض منفصل لا ينهار عند الإنهاء غير الطبيعيّ.

الملف الشخصي للمؤلف

صفحة الملف الشخصي لمؤلف المقالة.

غو كومورا

مؤسّس شركة كومورا سوفت ذ.م.م.

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

العودة إلى المدونة