0Pricing
C# Academy · درس

المهام المجدولة في Quartz.NET

عرّفوا المهام والمشغّلات باستخدام Quartz.NET، واستخدموا تعبيرات cron، وادمجوه مع DI في ASP.NET Core.

المهام المجدولة في Quartz.NET درس مجاني في C# Academy على CoddyKit. هذا هو الدرس 4 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في C# Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة C# Academy 4 دروس في المجموع.

لماذا Quartz.NET؟

عندما تحتاج إلى تعبيرات cron وحفظ المهام والتجميع وسياسات إعادة المحاولة وسلاسل المهام، يوفّر Quartz.NET أداة جدولة جاهزة للإنتاج تتجاوز ما تقدمه PeriodicTimer. وهو إصدار .NET من أداة جدولة Quartz الشائعة في Java.

تثبيت Quartz.NET

أضف حزمتَي Quartz وQuartz.AspNetCore. ويسجّل تكامل ASP.NET تلقائيًا أداة الجدولة كخدمة مستضافة.

# Install packages
dotnet add package Quartz
dotnet add package Quartz.AspNetCore

# Optional: persistence (requires Quartz.Serialization.Json too)
# dotnet add package Quartz.Jobs
# dotnet add package Quartz.Plugins

تعريف مهمة

نفّذ IJob (أو IAsyncJob في إصدارات Quartz الأحدث) مع أسلوب Execute. يوفّر سياق المهمة بيانات وقت التشغيل ومعلومات المشغّل الحالي.

using Quartz;

[DisallowConcurrentExecution] // prevent overlapping executions
public class ReportGeneratorJob : IJob
{
    private readonly IReportService _reports;
    private readonly ILogger<ReportGeneratorJob> _logger;

    public ReportGeneratorJob(IReportService r, ILogger<ReportGeneratorJob> l)
    { _reports = r; _logger = l; }

    public async Task Execute(IJobExecutionContext context)
    {
        _logger.LogInformation("Generating report at {Time}", DateTime.UtcNow);
        await _reports.GenerateDailyReportAsync(context.CancellationToken);
        _logger.LogInformation("Report generated");
    }
}

تسجيل Quartz في ASP.NET Core

استخدم AddQuartz لإعداد أداة الجدولة، وAddQuartzHostedService لتشغيلها كـ IHostedService. وتُضبط المهام والمشغّلات داخل lambda.

builder.Services.AddQuartz(q =>
{
    q.UseMicrosoftDependencyInjectionJobFactory();

    var jobKey = new JobKey("ReportGenerator");
    q.AddJob<ReportGeneratorJob>(opts => opts.WithIdentity(jobKey));

    q.AddTrigger(opts => opts
        .ForJob(jobKey)
        .WithIdentity("ReportTrigger")
        .WithCronSchedule("0 0 8 * * ?") // 8:00 AM daily
    );
});

builder.Services.AddQuartzHostedService(q =>
    q.WaitForJobsToComplete = true);

بنية تعبير cron

يستخدم Quartz تعبير cron مكوّنًا من 6 أو 7 حقول: seconds minutes hours day-of-month month day-of-week [year]. يختلف cron في Quartz قليلًا عن cron في Unix، إذ تأتي الثواني أولًا.

// Quartz cron format: sec min hour dom month dow [year]

"0 0 8 * * ?"     // Every day at 8:00 AM
"0 0/30 9-17 * * ?" // Every 30 min, 9 AM - 5 PM weekdays
"0 0 0 1 * ?"     // First day of every month at midnight
"0 0 6 ? * MON-FRI" // Every weekday at 6 AM
"0 0/5 * * * ?"   // Every 5 minutes

// Tip: use cronmaker.com or crontab.guru to build expressions

المشغّلات البسيطة

للجداول الزمنية القائمة على الفواصل من دون تعقيد cron، استخدم المشغّلات البسيطة مع WithSimpleSchedule.

q.AddTrigger(opts => opts
    .ForJob(jobKey)
    .WithIdentity("CleanupTrigger")
    .StartNow()
    .WithSimpleSchedule(s => s
        .WithIntervalInHours(1)
        .RepeatForever())
);

// Or a one-shot trigger:
q.AddTrigger(opts => opts
    .ForJob(jobKey)
    .WithIdentity("OneShot")
    .StartAt(DateTimeOffset.UtcNow.AddMinutes(5)));

خريطة بيانات المهمة

مرّر معلمات وقت التشغيل إلى المهام عبر JobDataMap. ويمكنك الوصول إليها داخل Execute من خلال context.JobDetail.JobDataMap.

q.AddJob<EmailJob>(opts => opts
    .WithIdentity("EmailJob")
    .UsingJobData("to", "admin@example.com")
    .UsingJobData("subject", "Daily Digest"));

// Inside the job:
public async Task Execute(IJobExecutionContext context)
{
    var to      = context.JobDetail.JobDataMap.GetString("to");
    var subject = context.JobDetail.JobDataMap.GetString("subject");
    await _mailer.SendAsync(to!, subject!, "body");
}

‏DisallowConcurrentExecution

تمنع السمة [DisallowConcurrentExecution] بدء مثيل جديد من المهمة أثناء استمرار تشغيل مثيل سابق. وهي ضرورية للمهام التي تصل إلى موارد مشتركة.

[DisallowConcurrentExecution]
public class InventorySyncJob : IJob
{
    // If a sync takes > 1 minute and triggers every minute,
    // the next trigger is delayed until this one completes

    public async Task Execute(IJobExecutionContext context)
    {
        await SyncInventoryAsync(context.CancellationToken);
    }
}

// Without [DisallowConcurrentExecution], multiple instances
// could run in parallel, causing data conflicts.

معالجة الاستثناءات وإعادة المحاولة

غلّف شيفرة المهمة في try/catch. اطرح JobExecutionException لإبلاغ Quartz بضرورة إعادة تشغيل المهمة فورًا أو بعد تأخير.

public async Task Execute(IJobExecutionContext context)
{
    try
    {
        await ProcessAsync(context.CancellationToken);
    }
    catch (TransientException ex)
    {
        // Ask Quartz to retry immediately
        var jobException = new JobExecutionException(ex)
        {
            RefireImmediately = true
        };
        throw jobException;
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "Job failed — not retrying");
        // Don't rethrow — job is done
    }
}

تطبيق واقعي: أداة جدولة متعددة المهام

إعداد Quartz جاهز للإنتاج، يضم مهام متعددة وفق جداول زمنية مختلفة، وجميعها تستخدم DI للوصول إلى الخدمات.

builder.Services.AddQuartz(q =>
{
    q.UseMicrosoftDependencyInjectionJobFactory();

    // Daily report at 8 AM
    var reportKey = new JobKey("DailyReport");
    q.AddJob<ReportGeneratorJob>(o => o.WithIdentity(reportKey));
    q.AddTrigger(o => o.ForJob(reportKey)
        .WithCronSchedule("0 0 8 * * ?"));

    // Inventory sync every 15 minutes
    var syncKey = new JobKey("InventorySync");
    q.AddJob<InventorySyncJob>(o => o.WithIdentity(syncKey));
    q.AddTrigger(o => o.ForJob(syncKey)
        .WithSimpleSchedule(s => s
            .WithIntervalInMinutes(15)
            .RepeatForever()));

    // Cache cleanup every night at 2 AM
    var cacheKey = new JobKey("CacheCleanup");
    q.AddJob<CacheCleanupJob>(o => o.WithIdentity(cacheKey));
    q.AddTrigger(o => o.ForJob(cacheKey)
        .WithCronSchedule("0 0 2 * * ?"));
});

تحقق سريع

ما وظيفة السمة [DisallowConcurrentExecution] عند استخدامها في مهمة Quartz.NET؟

مراجعة: المهام المجدولة باستخدام Quartz.NET

أهم النقاط:

  • ‏Quartz.NET: أداة جدولة جاهزة للإنتاج، تدعم cron وحفظ المهام والتجميع
  • عرّف المهام بتنفيذ IJob مع أسلوب Execute
  • سجّلها باستخدام AddQuartz + AddQuartzHostedService؛ إذ تدعم المهام DI
  • تنسيق cron: seconds minutes hours dom month dow — لاحظ أن الثواني تأتي أولًا
  • ‏[DisallowConcurrentExecution]: انتظر انتهاء التنفيذ الحالي قبل إعادة التشغيل
  • استخدم JobDataMap لتمرير معلمات وقت التشغيل إلى المهام

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

هل درس «المهام المجدولة في Quartz.NET» مجاني؟

نعم — نص درس «المهام المجدولة في Quartz.NET» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة C# Academy، انتقل إلى CoddyKit PRO. تتضمن دورة C# Academy 4 دروس في المجموع.

ماذا ستتعلم في «المهام المجدولة في Quartz.NET»؟

عرّفوا المهام والمشغّلات باستخدام Quartz.NET، واستخدموا تعبيرات cron، وادمجوه مع DI في ASP.NET Core. تتمرن على C# Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ C# Academy؟

لا تُشترط خبرة سابقة. C# Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 4 من أصل 4.

كم من الوقت يستغرق درس «المهام المجدولة في Quartz.NET»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس C# Academy هذا؟

نعم. كل درس في C# Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. IHostedService وBackgroundService
  2. مشاريع Worker Service
  3. المهام الدورية والمؤقتات
  4. المهام المجدولة في Quartz.NET
← العودة إلى C# Academy