IHostedService وBackgroundService
طبّقوا IHostedService وBackgroundService المجرّد لتشغيل الأعمال في الخلفية داخل مضيف .NET.
IHostedService وBackgroundService درس مجاني في C# Academy على CoddyKit. هذا هو الدرس 1 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في C# Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة C# Academy 4 دروس في المجموع.
ما الخدمات الخلفية؟
تشغّل الخدمات الخلفية أعمالًا طويلة الأمد بالتوازي مع تطبيق ASP.NET Core، مثل معالجة قوائم الانتظار وتشغيل المهام المجدولة أو مراقبة الموارد. يوفّر .NET واجهتين: IHostedService (الحد الأدنى) والصنف المجرد BackgroundService (المناسب للحلقات).
واجهة IHostedService
تحتوي IHostedService على أسلوبين: StartAsync (يُستدعى عند بدء تشغيل المضيف) وStopAsync (يُستدعى أثناء الإيقاف السلس). وهي مثالية لمهام بدء التشغيل والإيقاف التي تُنفّذ مرة واحدة.
public class DatabaseMigratorService : IHostedService
{
private readonly IServiceScopeFactory _scopeFactory;
public DatabaseMigratorService(IServiceScopeFactory sf) => _scopeFactory = sf;
public async Task StartAsync(CancellationToken ct)
{
using var scope = _scopeFactory.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await db.Database.MigrateAsync(ct);
}
public Task StopAsync(CancellationToken ct) => Task.CompletedTask;
}
builder.Services.AddHostedService<DatabaseMigratorService>();BackgroundService: الحلقات طويلة التشغيل
BackgroundService صنف مجرد ينفّذ IHostedService ويوفّر ExecuteAsync(CancellationToken)، وهو أسلوب يستمر طوال مدة تشغيل التطبيق.
public class HeartbeatService : BackgroundService
{
private readonly ILogger<HeartbeatService> _logger;
public HeartbeatService(ILogger<HeartbeatService> l) => _logger = l;
protected override async Task ExecuteAsync(CancellationToken ct)
{
_logger.LogInformation("Heartbeat service started");
while (!ct.IsCancellationRequested)
{
_logger.LogInformation("Heartbeat: {Time}", DateTime.UtcNow);
await Task.Delay(TimeSpan.FromSeconds(30), ct);
}
_logger.LogInformation("Heartbeat service stopping");
}
}
builder.Services.AddHostedService<HeartbeatService>();استخدام الخدمات ذات النطاق Scoped
الخدمات الخلفية هي Singletons. لاستخدام الخدمات ذات النطاق Scoped (مثل DbContext)، أنشئ دائمًا نطاقًا جديدًا لكل وحدة عمل باستخدام IServiceScopeFactory.
public class DataSyncService : BackgroundService
{
private readonly IServiceScopeFactory _factory;
private readonly ILogger<DataSyncService> _log;
public DataSyncService(IServiceScopeFactory f, ILogger<DataSyncService> l)
{ _factory = f; _log = l; }
protected override async Task ExecuteAsync(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
using (var scope = _factory.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await SyncDataAsync(db, ct);
} // scope and DbContext disposed here
await Task.Delay(TimeSpan.FromMinutes(5), ct);
}
}
}الإيقاف السلس
عند إيقاف تشغيل المضيف، يفعّل رمز الإلغاء. يجب أن يراقبه ExecuteAsync وينفّذ أعمال التنظيف. ينتظر المضيف مدة تصل إلى 5 ثوانٍ (افتراضيًا) قبل الإيقاف بالقوة.
protected override async Task ExecuteAsync(CancellationToken ct)
{
try
{
while (!ct.IsCancellationRequested)
{
await DoWorkAsync(ct);
await Task.Delay(1000, ct);
}
}
catch (OperationCanceledException)
{
// Normal shutdown — not an error
_logger.LogInformation("Service stopping due to cancellation");
}
finally
{
// Cleanup
await CleanupAsync();
}
}قائمة انتظار خلفية قائمة على Channel
نمط شائع: تقرأ الخدمة الخلفية من قائمة انتظار Channel، بينما تضيف نقاط نهاية HTTP عناصر العمل إليها. وهذا يفصل بين معالجة الطلبات ومعالجة العمل.
public class BackgroundTaskQueue
{
private readonly Channel<Func<CancellationToken, Task>> _queue
= Channel.CreateBounded<Func<CancellationToken, Task>>(100);
public ValueTask QueueAsync(Func<CancellationToken, Task> job)
=> _queue.Writer.WriteAsync(job);
public IAsyncEnumerable<Func<CancellationToken, Task>> DequeueAllAsync(
CancellationToken ct)
=> _queue.Reader.ReadAllAsync(ct);
}
// Register and use:
builder.Services.AddSingleton<BackgroundTaskQueue>();
builder.Services.AddHostedService<QueueProcessorService>();معالجة الأخطاء في الخدمات الخلفية
قد تؤدي الاستثناءات غير المعالجة في ExecuteAsync إلى إيقاف الخدمة بصمت. غلّف الحلقة الرئيسية في try/catch وسجّل الأخطاء لإبقاء الخدمة قيد التشغيل رغم حالات الفشل الفردية.
protected override async Task ExecuteAsync(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
try
{
await ProcessNextBatchAsync(ct);
}
catch (OperationCanceledException) when (ct.IsCancellationRequested)
{
break; // normal shutdown
}
catch (Exception ex)
{
_logger.LogError(ex, "Background processing error. Retrying in 5s.");
await Task.Delay(5000, ct); // backoff before retry
}
}
}StopAsync ومهلة الإيقاف
تجاوز StopAsync لتنفيذ أعمال التنظيف. يمرّر المضيف رمزًا مخصصًا للإيقاف؛ وإذا استغرق التنظيف وقتًا أطول من المهلة، تُنهى العملية بالقوة مهما كان السبب.
public override async Task StopAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Service is stopping...");
// Signal internal work to stop
_internalCts.Cancel();
// Wait for the Execute loop to complete (up to timeout)
await base.StopAsync(stoppingToken);
_logger.LogInformation("Service stopped");
}
// Increase the shutdown timeout if needed:
builder.Services.Configure<HostOptions>(opt =>
opt.ShutdownTimeout = TimeSpan.FromSeconds(30));خدمات خلفية متعددة
سجّل عدة خدمات مستضافة، إذ تعمل جميعها بالتزامن. يبدأها المضيف بالترتيب ويوقفها بالترتيب العكسي.
builder.Services.AddHostedService<DatabaseMigratorService>(); // startup task
builder.Services.AddHostedService<MetricsCollectorService>(); // continuous
builder.Services.AddHostedService<EmailNotificationService>(); // continuous
builder.Services.AddHostedService<CacheWarmupService>(); // startup task
// All run concurrently after app start
// Stopped in reverse registration order on shutdownتطبيق واقعي: Worker لمعالجة الطلبات
خدمة خلفية تعالج الطلبات المعلّقة من قائمة انتظار في قاعدة البيانات، مع إنشاء النطاق المناسب ومعالجة الأخطاء.
public class OrderProcessorService : BackgroundService
{
private readonly IServiceScopeFactory _factory;
private readonly ILogger<OrderProcessorService> _log;
public OrderProcessorService(IServiceScopeFactory f, ILogger<OrderProcessorService> l)
{ _factory = f; _log = l; }
protected override async Task ExecuteAsync(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
try
{
using var scope = _factory.CreateScope();
var processor = scope.ServiceProvider.GetRequiredService<IOrderProcessor>();
int count = await processor.ProcessPendingAsync(ct);
_log.LogInformation("Processed {Count} orders", count);
}
catch (Exception ex) when (!ct.IsCancellationRequested)
{
_log.LogError(ex, "Order processing failed");
}
await Task.Delay(TimeSpan.FromSeconds(10), ct);
}
}
}تحقق سريع
لماذا يجب أن تستخدم الخدمات الخلفية IServiceScopeFactory بدلًا من حقن DbContext مباشرةً؟
مراجعة: IHostedService وBackgroundService
أهم النقاط:
IHostedService: StartAsync/StopAsync — لمهام بدء التشغيل والإيقافBackgroundService: حلقة ExecuteAsync — للعمل الخلفي المستمر- استخدم دائمًا
IServiceScopeFactoryللتبعيات ذات النطاق Scoped داخل الخدمات الخلفية - راقب رمز الإلغاء عند كل await لتحقيق إيقاف سلس
- غلّف جسم الحلقة في try/catch لمنع توقف الخدمة عند حدوث أخطاء
- استخدم
HostOptions.ShutdownTimeoutإذا احتاج التنظيف إلى أكثر من 5 ثوانٍ
الأسئلة الشائعة
هل درس «IHostedService وBackgroundService» مجاني؟
نعم — نص درس «IHostedService وBackgroundService» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة C# Academy، انتقل إلى CoddyKit PRO. تتضمن دورة C# Academy 4 دروس في المجموع.
ماذا ستتعلم في «IHostedService وBackgroundService»؟
طبّقوا IHostedService وBackgroundService المجرّد لتشغيل الأعمال في الخلفية داخل مضيف .NET. تتمرن على C# Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ C# Academy؟
لا تُشترط خبرة سابقة. C# Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 1 من أصل 4.
كم من الوقت يستغرق درس «IHostedService وBackgroundService»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس C# Academy هذا؟
نعم. كل درس في C# Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- IHostedService وBackgroundService
- مشاريع Worker Service
- المهام الدورية والمؤقتات
- المهام المجدولة في Quartz.NET