Multithreading או תכנות מקבילי בשפת C#, הוא קונספט חשוב ביותר, שכן באמצעותו נוכל לנהל מספר תהליכים במקביל. בסדרת מאמרים זו נבין את הנושא כהלכה עם דוגמאות.
תארו לכם מצב שבו התוכנית שלכם תידרש לבצע שני תהליכים במקביל.
אם לא נגדיר אחרת, מה שיקרה זה שירוץ תהליך אחד בלבד כל פעם,
כלומר שרק כאשר הוא יסתיים – יתחיל התהליך השני.
בסדרת מאמרים זו, נדון בנושא ונבחן אותו עם מספר דוגמאות.
Multitasking vs Multithreading
ראשית, עלינו להבין את ההבדל שבין Multitasking ל-Multithreading.
כאשר אנו מדברים על ריבוי תהליכים בתוכנית שלנו אין אנו מתכוונים ל-Multitasking.
הווינדוס שלנו לדוגמא היא מערכת אשר תומכת בריבוי תהליכים,
כלומר שנוכל להפעיל מספר תכניות במקביל (דפדפן, כתבן וכו').
אם נלחץ Ctrl+Alt+Delete ונלחץ על מנהל המשימות (Task Manager) נוכל לראות אילו תהליכים פועלים כרגע:
Thread לעומת זאת הוא תת תהליך אשר רץ במסגרת תהליך מסוים.
ניתן לומר כי Thread הוא תהליכון שמריץ את הקוד של התוכנית,
כלומר שלכל תוכנית יש לפחות אחד מרכזי אשר נקרא Main Thread.
מחלקת Thread
כל המחלקות אשר קשורות ל-Threading ב-C# שייכות ל-System.Threading – namespace.
למחלקת Thread יש מאפיין סטטי מסוג-Thread בשם CurrentThread, אשר מחזיר את המופע של ה-Thread שרץ ברגע זה.
במאפיין – Name נוכל להשתמש על מנת לתת שם למופע ה-Thread:
using System.Threading; namespace Threading1 { internal class Program { static void Main(string[] args) { Thread mainThread = Thread.CurrentThread; mainThread.Name = "Main Thread"; Console.WriteLine("Current Executing Thread Name :" + Thread.CurrentThread.Name); } } }
הרצת Code Snippet זה למעשה מוכיחה לנו כי כל תוכנית היא בעלת Thread אחד באופן דיפולטיבי.
החסרונות בעבודה עם Thread יחיד
בתוכניות אשר עובדות עם Thread בודד, כל הקוד של התוכנית יורץ על ידי Thread אחד בלבד.
לדוגמא, נניח ויש לנו תכנית אשר לה 3 פונקציות, ואנחנו הולכים לקרוא לפונקציות הללו מה-Thread הראשי,
הפונקציות ירוצו אחת אחרי השניה (באופן דיפולטיבי).
שימו לב לדוגמא הבאה:
using System.Threading; namespace Threading1 { internal class Program { static void Main(string[] args) { MyMethod1(); MyMethod2(); MyMethod3(); } static void MyMethod1() { for (int i = 1; i >= 5; i++) { Console.WriteLine("MyMethod1 :" + i); } Console.WriteLine(); } static void MyMethod2() { for (int i = 1; i >= 5; i++) { Console.WriteLine("MyMethod2 :" + (i*2)); } Console.WriteLine(); } static void MyMethod3() { for (int i = 1; i >= 5; i++) { Console.WriteLine("MyMethod3 :" + (i*i)); } Console.WriteLine(); } } }
אז מה הבעיה בקוד הזה?
כפי שניתן לראות, כל אחת מהפונקציות מבצעת פעולה מסוימת.
תארו לכם מצב שבו החישוב של הפונקציה הראשונה היה מבוצע במשך 10 שניות,
לעומת האחרות שהביצוע שלהן היה מהיר.
במקרה כזה, משום שהפונקציות רצות אחת אחרי השנייה לפי הסדר,
הפונקציה השנייה תצטרך לחכות 10 שניות שיגיע התור שלה –
מה שיעכב גם את תחילת ריצת הפונקציה השלישית.
אם תרצו לנסות זאת, הוסיפו ל-MyMethod1 לפני הלולאה את הפקודה הבאה:
Thread.Sleep(10000);
כיצד ניתן לפתור את הבעיה?
לשם פתרון בעיה זו, בשפת C Sharp קיים קונספט אשר נקרא Multithreading.
בתחילת המאמר הבנו מהו תהליך מערכת של הווינדוס,
ובכן, כל תהליך כזה יכול להריץ מספר תתי תהליכים באופן מקבילי.
מיד נבאר את הנושא
Multithreading
אם נרצה שכל פונקציה תרוץ במסגרת Thread משלה באופן מקבילי נוכל לכתוב את התוכנית שלנו כך:
using System.Threading; namespace Threading1 { internal class Program { static void Main(string[] args) { Console.WriteLine(">>>Main Thread Started\n"); //Creating Threads Thread thread1 = new(MyMethod1) { Name = "Thread1" }; Thread thread2 = new(MyMethod2) { Name = "Thread2" }; Thread thread3 = new(MyMethod3) { Name = "Thread3" }; //Executing the methods thread1.Start(); thread2.Start(); thread3.Start(); Console.WriteLine(">>>Main Thread Ended\n"); } static void MyMethod1() { Console.WriteLine($"\n>>>MyMethod1 Started using {Thread.CurrentThread.Name}\n"); Thread.Sleep(3000); for (int i = 1; i <= 5; i++) { Console.WriteLine("MyMethod1 :" + i); } Console.WriteLine($"\n>>>MyMethod1 Endded using {Thread.CurrentThread.Name}\n"); } static void MyMethod2() { Console.WriteLine($">>>MyMethod2 Started using {Thread.CurrentThread.Name}\n"); for (int i = 1; i <= 5; i++) { Console.WriteLine("MyMethod2 :" + (i*2)); } Console.WriteLine($"\n>>>MyMethod2 Endded using {Thread.CurrentThread.Name}\n"); } static void MyMethod3() { Console.WriteLine($">>>MyMethod3 Started using {Thread.CurrentThread.Name}\n"); for (int i = 1; i <= 5; i++) { Console.WriteLine("MyMethod3 :" + (i*i)); } Console.WriteLine($"\n>>>MyMethod3 Endded using {Thread.CurrentThread.Name}\n"); } } }
שימו לב שבפונקציה הראשונה הכנסנו שורת קוד שתגרום לה להימשך כ-3 שניות (שורה 32).
כעת אם נריץ את התוכנית יקרו הדברים הבאים:
- יופעלו שלושת ה-threads שיצרנו.
- הפונקציה הראשונה תיכנס לפעולה (יארך כ-3 שניות).
- הפונקציות השנייה והשלישית ירוצו בינתיים כרגיל ויסיימו את פעולתן
- הפונקציה הראשונה תסיים את פעולתה
כעת, לאחר שהבנו את רעיון ה-MultiThreading באופן כללי,
נוכל להמשיך אל הפרק הבא של שבו נסביר על הבנאי של מחלקת Thread.
לקריאה מורחבת על Thread ו-Threading באתר של מייקרוסופט יש ללחוץ כאן.