التزامن في C#: الخيوط، البرمجة غير المتزامنة، والجوروتينات عمليًا

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

التزامن في C#: كيف تجعل برنامجك يعمل بكفاءة

\n\n

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

\n\n

في لغة C#، هناك ثلاث أدوات رئيسية لتحقيق ذلك: الخيوط التقليدية (Thread)، المهام الحديثة (Task) مع async/await، ومفهوم الجوروتينات المنفذ عبر المكررات (iterators). في هذا المقال، سنشرح كل نهج، ونقارن أداءها، ونوضح متى نستخدم كل منها. إذا كنت تكتب كودًا بلغة C# وتريد أن يكون سريعًا وفعالًا، فأنت في المكان الصحيح.

\n\n

الخيوط (Thread): المدفعية الثقيلة

\n\n

الخيط هو الطريقة الأكثر تدنيًا لتنظيم التزامن. كل خيط هو "عملية خفيفة" منفصلة داخل برنامجك، لها مكدس (stack) وسياق (context) خاص بها. إنشاء خيط في C# بسيط جدًا:

\n\n
using System;\nusing System.Threading;\n\nclass Program\n{\n    static void Main()\n    {\n        Thread thread = new Thread(DoWork);\n        thread.Start();\n        Console.WriteLine("الخيط الرئيسي يعمل");\n        thread.Join(); // انتظار الانتهاء\n    }\n\n    static void DoWork()\n    {\n        for (int i = 0; i < 5; i++)\n        {\n            Console.WriteLine($"الخيط: {i}");\n            Thread.Sleep(200);\n        }\n    }\n}
\n\n

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

\n\n

عيوب الخيوط

\n
    \n
  • ثقيلة الوزن. كل خيط يحجز حوالي 1 ميجابايت من الذاكرة للمكدس. إنشاء 1000 خيط يعني شغل 1 جيجابايت من الذاكرة دون داعٍ.
  • \n
  • صعوبة التزامن. لمنع خيطين من إتلاف البيانات المشتركة، تحتاج إلى أقفال (locks)، وميوتكسات (mutexes)، وسيمفورات (semaphores). خطأ واحد يؤدي إلى deadlock أو race condition.
  • \n
  • تبديل السياق. نظام التشغيل يستهلك وقتًا في التبديل بين الخيوط. إذا كان عدد الخيوط أكبر من عدد أنوية المعالج، يصبح هذا عنق زجاجة.
  • \n
\n\n

الخيوط جيدة عندما تحتاج إلى توازٍ حقيقي على معالج متعدد النواة للحسابات الثقيلة. لكنها زائدة عن الحاجة لعمليات الإدخال/الإخراج (التعامل مع الملفات، الشبكة، قواعد البيانات).

\n\n

Task و async/await: المعيار الحديث

\n\n

بدءًا من .NET 4.5، قدمت Microsoft بديلًا أنيقًا - مكتبة المهام المتوازية (TPL) والكلمات المفتاحية async/await. هذه ليست خيوطًا بالمعنى التقليدي، بل وعود (promises): تخبر المترجم "قم بتشغيل هذه المهمة، وعندما تنتهي، أعد النتيجة". أثناء تنفيذ المهمة، لا يتم حظر الخيط، بل يعود إلى مجمع الخيوط (thread pool).

\n\n
using System;\nusing System.Net.Http;\nusing System.Threading.Tasks;\n\nclass Program\n{\n    static async Task Main()\n    {\n        Console.WriteLine("بدء التحميل...");\n        string data = await DownloadDataAsync();\n        Console.WriteLine($"تم التحميل: {data.Length} حرفًا");\n    }\n\n    static async Task<string> DownloadDataAsync()\n    {\n        using HttpClient client = new HttpClient();\n        string result = await client.GetStringAsync("https://example.com");\n        return result;\n    }\n}
\n\n

لاحظ أن await لا يحجب الخيط. أثناء تحميل البيانات عبر الشبكة، يعود الخيط إلى المجمع ويمكنه خدمة طلبات أخرى. هذا مثالي لخوادم الويب وتطبيقات الواجهات الرسومية - لا تتجمد الواجهة.

\n\n

كيف يعمل هذا تحت الغطاء؟

\n

عندما يرى المترجم async، يحول الطريقة إلى آلة حالة محدودة (state machine). عند كل await، "تتذكر" الطريقة حالتها

المدونات

توصيات الكتب