בפרקים הקודמים של המדריך ראינו כיצד ביכולתנו לבצע Multithreading. בפרק זה נלמד על Join & IsAlive וניווכח כיצד נוכל להיעזר בהם.
אם נחזור לרגע לתוכנית אשר כתבנו בפרק הראשון של המדריך,
נוכל להבחין שהמתודה הראשונה לא ממתינה שהשנייה והשלישית יסיימו את פעולתן,
כלומר שהן רצות בצורה מקבילית.
חשוב להבין כי Thread 1,2,3 הם למעשה יורשים של ה-Thread המרכזי,
וזאת משום שהן נוצרו על ידיו.
כלומר שה-Thread המרכזי לא יסיים את פעולתו – עד שה-Threads יסיימו את פעולתם.
שימו לב לדוגמא זו אשר נלקחה מהפרק הראשון:
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"); } } }
הרצת קוד זה למעשה מראה לנו שאכן כך הדבר.
אך מה אם היינו רוצים דווקא לחסום Thread מסוים מלרוץ עד אשר ה-Thread היורש יסיים את פעולתו?
המתודה ()Join
במתודה Join נוכל להשתמש על מנת להשהות את ה-Thread הנוכחי עד אשר יורשיו יסיימו את פעולתם.
ישנם 3 העמסות למתודה Join במחלקת Thread:
- ()Join – משהה את ה-Thread אשר קרא למתודה – עד אשר ה-Threads שיורשים ממנו יסיימו את פעולתם.
- Join(int millisecondsTimeout) – משהה את ה-Thread אשר קרא למתודה – עד אשר ה-Threads שיורשים ממנו יסיימו את פעולתם, או שמגיע זמן ה-Timeout שהעברנו בפרמטר,
אם ה-Thread ימשיך לרוץ לאחר תום הזמן שהוגדר – תיזרק חריגת ArgumentOutOfRangeException,
נוכל לתת כערך את System.Threading.Timeout.Infinite על מנת שהזמן המוגדר יהיה אינסופי.
אם ה-Thread לא יתחיל לרוץ בכלל – תיזרק חריגת ThreadStateException. - Join(TimeSpan timeout) – זהה לקודם, ההבדל הוא ביחידות הזמן שנעביר כפרמטר
שימו לב ל-Code Snippet הבא:
using System.Threading; namespace ThreadingDemo { class Program { static void Main(string[] args) { Console.WriteLine("Main Thread Started"); //Main Thread creating three child threads Thread thread1 = new(MyMethod1); Thread thread2 = new(MyMethod2); Thread thread3 = new(MyMethod3); thread1.Start(); thread2.Start(); thread3.Start(); thread1.Join(); thread2.Join(); thread3.Join(); Console.WriteLine("Main Thread Ended"); } static void MyMethod1() { Console.WriteLine("MyMethod1 - Thread1 Started"); Thread.Sleep(5000); Console.WriteLine("MyMethod1 - Thread1 Ended"); } static void MyMethod2() { Console.WriteLine("MyMethod2 - Thread2 Started"); Thread.Sleep(2000); Console.WriteLine("MyMethod2 - Thread2 Ended"); } static void MyMethod3() { Console.WriteLine("MyMethod3 - Thread3 Started"); Thread.Sleep(1000); Console.WriteLine("MyMethod3 - Thread3 Ended"); } } }
אם תריצו קטע קוד זה, תוכלו להבחין כי ה-Thread המרכזי לא סיים את פעולתו עד אשר יורשיו סיימו את פעולתם, בדיוק כפי שהוסבר:
כלומר שאם לא היינו מעוניינים לחכות ל-Thread1 (שהפעולה שלו אורכת כ-5 שניות), פשוט לא היינו עושים לו Join.
אם תסירו את שורה 19 ותריצו שוב, תתקבל תוצאה דומה לזו:
המאפיין IsAlive
במאפיין זה נוכל להשתמש על מנת לקבל את הסטטוס של ה-Thread.
אם מחזיר לנו True – ה-Thread אכן רץ,
אם מחזיר לנו False -סימן שה-Thread סיים את פעולתו.
שימו לב לדוגמא הבאה שבה ניישם קונספט זה על ידי שימוש במתודה משותפת ל-Threads:
using System.Threading; namespace ThreadingDemo { class Program { static void Main(string[] args) { Console.WriteLine("Main Thread Started"); //Main Thread creating three child threads Thread thread1 = new(MyMethod1) { Name = nameof(MyMethod1) }; Thread thread2 = new(MyMethod2) { Name = nameof(MyMethod2) }; Thread thread3 = new(MyMethod3) { Name = nameof(MyMethod3) }; thread1.Start(); thread2.Start(); thread3.Start(); Console.WriteLine("\n************* IsAlive Check: ******************"); IsAliveCheck(thread1); IsAliveCheck(thread2); IsAliveCheck(thread3); Console.WriteLine("***********************************************\n"); thread2.Join(); thread3.Join(); Console.WriteLine("\n************* IsAlive Check: ******************"); IsAliveCheck(thread1); IsAliveCheck(thread2); IsAliveCheck(thread3); Console.WriteLine("***********************************************\n"); Console.WriteLine("Main Thread Ended"); Console.WriteLine("\n************* IsAlive Check: ******************"); IsAliveCheck(thread1); IsAliveCheck(thread2); IsAliveCheck(thread3); Console.WriteLine("***********************************************\n"); } static void IsAliveCheck(Thread thread) { if (thread.IsAlive) { Console.WriteLine($"{thread.Name} is working"); } else { Console.WriteLine($"{thread.Name} is not working"); } } static void MyMethod1() { Console.WriteLine("MyMethod1 - Thread1 Started"); Thread.Sleep(5000); Console.WriteLine("MyMethod1 - Thread1 Ended"); } static void MyMethod2() { Console.WriteLine("MyMethod2 - Thread2 Started"); Thread.Sleep(2000); Console.WriteLine("MyMethod2 - Thread2 Ended"); } static void MyMethod3() { Console.WriteLine("MyMethod3 - Thread3 Started"); Thread.Sleep(1000); Console.WriteLine("MyMethod3 - Thread3 Ended"); } } }
בדקנו את הסטטוס של ה-Threads שיצרנו בכל אחד משלבי התכנית,
ולכן קיבלנו את התוצאה הבאה:
תוכלו להבחין במספר דברים:
- מהסיבה שיש בתוכנית זו מתודות שרצות במקביל ללא טיפול סינכרוני – ההדפסה של Thread3 נכנסה לתוך ההדפסה של – IsAliveCheck.
- בזמן הבדיקה האחרונה שביצענו, Thread1 טרם סיים את פעולתו.
- Thread1 סיים את פעולתו לאחר שה-Thread המרכזי סיים את פעולתו – תקין לדעתכם?
כעת, לאחר שהבנו את אופן הפעולה של Join & IsAlive,
בפרק הבא של מדריך זה נלמד על סינכרוניות בתכנות מקבילי על מנת שנוכל לקבל תשובות לשאלות שנשארו פתוחות כתוצאה ממאמר זה.
לקריאה מורחבת על Thread ו-Threading באתר של מייקרוסופט יש ללחוץ כאן.