لماذا يحتاج تطبيق C# الخاص بك إلى قوائم الانتظار والمهام الخلفية؟
\n\nتخيل: تقوم بتشغيل تطبيق ويب بلغة C#، ويضغط المستخدم على زر "إرسال التقرير"، و... يتجمد الخادم لمدة 30 ثانية أثناء إنشاء ملف PDF. هل هذا مألوف؟ في عام 2024، لا يغفر المستخدمون البطء. الحل هو قوائم الانتظار والمهام الخلفية. إنها تحول الجحيم المتزامن إلى جنة غير متزامنة.
\n\nفي هذه المقالة، سنشرح كيفية تنظيم المعالجة الخلفية بشكل صحيح في لغة C#: بدءًا من Channel<T> المدمج وصولاً إلى RabbitMQ الصناعي. ستتعلم كيفية نقل العمل الثقيل من الخيط الرئيسي دون كسر البنية. هيا بنا!
ما هي قائمة انتظار المهام ولماذا هي مهمة في C#؟
\n\nقائمة الانتظار (queue) هي بنية بيانات FIFO (أول من يدخل، أول من يخرج). في سياق لغة C#، هي آلية تسمح بتأجيل تنفيذ مهمة ما لتجنب حظر الخيط الرئيسي. المهمة الخلفية هي كود يتم تنفيذه "خلف الكواليس" بينما يستمر المستخدم في النقر على الموقع.
\n\nالسيناريوهات النموذجية:
\n- \n
- إرسال إشعارات البريد الإلكتروني (100 رسالة = 100 طلب إلى SMTP) \n
- إنشاء التقارير وملفات Excel \n
- معالجة الصور المرفوعة (تغيير الحجم، العلامة المائية) \n
- التكامل مع واجهات برمجة التطبيقات الخارجية (استجابات بطيئة) \n
- تسجيل البيانات في قاعدة البيانات (لتجنب إبطاء واجهة المستخدم) \n
بدون قوائم الانتظار، سيتجمد تطبيق C# عند كل طلب ثقيل. مع قوائم الانتظار، تقوم ببساطة بوضع المهمة في المخزن المؤقت وترد على المستخدم: "تم، نحن نعالج".
\n\nالخيار 1: المهام الخلفية عبر BackgroundService (مدمج في .NET)
\n\nأبسط طريقة هي استخدام BackgroundService من Microsoft.Extensions.Hosting. هذه هي الفئة الأساسية لإنشاء الخدمات الخلفية في ASP.NET Core. وهي مثالية للسيناريوهات البسيطة عندما لا تكون هناك حاجة إلى توجيه معقد للرسائل.
مثال: معالج خلفي مع Channel<T>
\n\nلنكتب منتج-مستهلك بسيط. المنتج (وحدة التحكم) يضع المهمة في القناة، والمستهلك (BackgroundService) يسحبها وينفذها.
\n\n// الخطوة 1. إنشاء قناة (قائمة انتظار آمنة للخيوط)\npublic class TaskChannel\n{\n private readonly Channel<string> _channel = \n Channel.CreateBounded<string>(new BoundedChannelOptions(100)\n {\n FullMode = BoundedChannelFullMode.Wait\n });\n\n public ChannelWriter<string> Writer => _channel.Writer;\n public ChannelReader<string> Reader => _channel.Reader;\n}\n\n// الخطوة 2. BackgroundService الذي يقرأ من القناة\npublic class EmailBackgroundService : BackgroundService\n{\n private readonly TaskChannel _taskChannel;\n\n public EmailBackgroundService(TaskChannel taskChannel)\n {\n _taskChannel = taskChannel;\n }\n\n protected override async Task ExecuteAsync(CancellationToken stoppingToken)\n {\n await foreach (var emailData in _taskChannel.Reader.ReadAllAsync(stoppingToken))\n {\n // محاكاة إرسال بريد إلكتروني (ثانيتان)\n await Task.Delay(2000, stoppingToken);\n Console.WriteLine($"تم إرسال البريد الإلكتروني: {emailData}");\n }\n }\n}\n\n// الخطوة 3. وحدة التحكم التي تضع المهام\n[ApiController]\n[Route("api/email")]\npublic class EmailController : ControllerBase\n{\n private readonly TaskChannel _taskChannel;\n\n public EmailController(TaskChannel taskChannel)\n {\n _taskChannel = taskChannel;\n }\n\n [HttpPost("send")]\n public IActionResult SendEmail([FromBody] string emailData)\n {\n _taskChannel.Writer.TryWrite(emailData);\n return Accepted(new { message = "تم وضع البريد الإلكتروني في قائمة الانتظار" });\n }\n}\n\n// الخطوة 4. التسجيل في Program.cs\nbuilder.Services.AddSingleton<TaskChannel>();\nbuilder.Services.AddHostedService<EmailBackgroundService>();\n\nماذا يحدث هنا؟ يقوم المستخدم باستدعاء POST /api/email/send - يتم وضع البيانات في القناة