Personal C Sharp                                                         By  famsoft.org
Home How To Start Examples-Desktop Examples-Web PC# Methods Reference-Desktop Reference-Web


DEMONSTRATIVE EXAMPLES ====================== EXAMPLES ON THREADS =================== Working with threads is normally very hard, but with PC#, you can create as many threads as you like and write 100% thread safe code with minimum worry. How? The unique PC# programming style has made it possible. Assuming that you have already looked at most of the "PC# Demonstrative Examples", you must have noticed the following: (1) You rarely need to define new variables in your program. The few variables which are used for communication between your program and PC# methods are mostly all you need. (2) All PC# methods are uniform. All methods expect one string (the mode) as the only parameter and all return no value (void). Also all method names are made of one unique character followed with the method type indicator 'm'. SOURCES OF DATA CORRUPTION: --------------------------- (1) Use of common variables: When two threads access a common variable, there is always the possibility of data corruption. The reason is simple. If "thread 1" wanted to calculate (y=x+1) where x=5, it may do it in two steps as follows: step 1:x=5; Step 2: y=x+1; Now, immediately after the first step has been executed, if "thread 2" changes the value of (x) to 0, the result "thread 1" will get is going to be be 1 instead of the expected value of 6. (2) Use of common methods: For similar reason we should expect data corruption to happen if two threads access the same method at the same time. (3) Accessing common resource: Imagine if a bank program uses different threads to serve different customers and a man and his wife access the same account at the same time. The man wanted to withdraw $100, so his thread checked his balance and found $100 there so the transaction was flagged "acceptable". His wife immediately did the same, so her thread also checked the same account's balance and found the same $100 there. So her transaction was also flagged "accetable". Both received their cash and their bank ended with a big problem. HOW ARE THREADS CREATED: ------------------------ PC# identifies threads by their serial number. Whenever you like to create a new thread you call xm("tc") supplying it with the number you like to identify the thread with. The thread created will be named with the number you have supplied. When the thread starts running, the first thing it does is to declare a new variable (t) of type "int" to store its number into, then it analyses its name to know its number and stores it into (t) As you can see, (t) is unique for each thread, so it's thread safe and is used for communicating with the thread safe method mm() which will be discussed later. Thread number zero is the main thread which your program starts at and where all other threads are created. When C# creates a new thread, it requires supplying it with the name of the method which the thread will execute when it starts. In PC# all threads excecute method run() when they start. So, it becomes a question of which block number to start at within method run() Your program supplies xm("tc") also with the block number which the thread should start at assigned to (bli) Setting the maximum number of threads to be created: ---------------------------------------------------- Multi-threads programs consume plenty of system resources, so you must be careful. By default, the highest number of threads to use is one, which means that class (pcs) is set for single thread mode. Before you create threads, you need to count the total number of threads to be created and add one more for the creator (thread zero), assign the total to (o) and call method dm("ht") from inside method init() in order to change that setting. As an example, if you intend to create 4 threads, include the code [o=5;dm("ht");] into method init(). The thread numbers should be all between (1:4) No thread number should go beyond this range. HOW COULD PC# OVERCOME THE DATA CORRUPTION PROBLEMS? ---------------------------------------------------- (1) Use of common variables: Fortunately, The variables which are used by your program to communicate with PC# methods are limited in number. So PC# has created special arrays called "variable groups" allowing each thread to have its own private variable of each kind. for example group osg[] is a grpup of (os)'s. Each thread has its own copy of the variable (os) stored at row osg[t] where (t) is the thread number. So, when you write a multi-thread program and like to display the string "Hello World", you replace the line of code [os="Hellow World";tm("dl");] with the thread safe one [osg[t]="Hello World";mm(t,"tdl");]. Method mm() will be explaind next. All you need to know now is that since each thread has its own thread number (t), osg[t] is unique for each thread, so data corruption caused by accessing common variables is impossible to happen. The group names are made by adding the lower case char 'g' to the name of the variable if it was a single value variable, and by adding the upper case char 'G' if it was a multi-value varriable like an array. Here is a table to show you how to form the thread safe variable names: REGULAR: i ls lf id ib cls dnb blp OS THREAD-SAFE: ig[t] lsg[t] lfg[t] idg[t] ibg[t] clsg[t] dnbg[t] blpg[t] OSG[t] Each variable which is used for input or output of any PC# methods has its thread-safe group name. The only exception is (bli) "requested block number" which we need only one copy of. This is because all threads other than the main thread are confined to one single block. They are not allowed to jump between blocks so they don't need a thread- safe (bli). However, there is a thread safe group for (blp) "present block number" which is (blpg) (2) Use of common methods: In order to prevent data corruption which hapens when more than one thread access the same method, programmers use the "lock(this)" directive, to allow only one thread to access the method at one time. Although this is necessary when you work with multiple threads, it slows down program execution. Classes (pcs) and (pasp) are made mainly for single thread uses. All PC# methods are not thread safe. They work efficiently with single thread programs, but they cannot handle multi-thread ones. So, how can we handle multiple threads while keeping them the same for single thread use? The answer is the "Universal Method mm()". Method mm() supplies a thread safe access to all other PC# methods. When a thread likes to call any PC# method through the thread-safe method mm(), it supplies it with two items, its thread number (t) and the mode string. The mode string is made by adding the first char of the method's name to the mode string required. Let us have an example: To call tm("dl") in a thread safe manner call mm(t,"tdl") (t) is the current thread number and "tdl" is made of two parts, "t" means call method tm() and "dl" is the mode to call tm() at. In order to insure complete thread safety, method mm() uses the thread-safe group variables discussed above for both input and output. In order to give you a clear picture, let us see what goes inside method mm() when it executes the code: osg[t]="Hello World";mm(t,"tdl"); public void mm(int t,string md) { lock(this) { // Lock the instance to serve one thread at a time os=osg[t]; // Convert all input values from private to common tm("dl"); // Call wanted method osg[t]=os; // Convert all output values from common to private Monitor.Pulse(this); // Wake-up the older thread which has been waiting Monitor.Wait(this,10); // Wait 10 milliseconds to allow other threads // a fair share. } // Unlock the object } // Return to calling program (3) Accessing common resources: Now you must agree that accessing method mm() is 100% thread safe, but how can we overcome the problem arising from accessing a common resource like a "file" as we have discussed before? The answer is to keep the current object locked while a full block of code or a full method is being executed. Using the same example which has been used before, if the banking program has done so, it could have kept the wife from accessing the withdrawl method until her husband's transaction has been completed and the balance has been updated. Of course, you can do so by yourself, however PC# likes to keep your program easy, simple and error free, so it has developed a mechanism to do this for you. All you need to do, is to write a method containing the code which must be executed in full by one thread while others are blocked and name this method one of the 10 pre-defined method names "m0(), m1(),...or m9()". Then access this method through the universal thread-safe method mm() which will do the object locking for you. Fortunately, the method should be written as if it was made for single thread operation. Within the method, you use common variables and call any PC# method directly. So you get thread safety without working for it. Methods m0():m9() can require either one or two parameters when called. For example you can write method m0() in two different formats: public override m0(int t) {.....} or: public override m0(int t,string md) {.....} The "int" variable expects the thread number and the string variable expects the mode. We'll see some examples on using these methods later. ========================================================================================= Example 1: This program will create 7 threads (threads 1:7) in addition to the main thread (thread 0) Each thread will display 2 messages in a unique color telling us its number. ========================================================================================= public class a:pcs { public override void init() { o=8;dm("ht"); // Max threads=8 (including thread 0) tia=toa="t"; bli=0; base.init(); } public override void run() { //---- The following three lines must be included into any multi-thread program ----- int t; // Thread # Defined here so each thread Thread thp=Thread.CurrentThread; // will have its own unique copy. t=Int32.Parse(thp.Name); // Value of (t) for current thread //----------------------------------------------------------------------------------- if (blpg[t]==0) { os="Hello, this is thread number zero, I'll be creating 7 threads.";tm(); os="Each thread will be introducing itself twice.";tm(); // No thread safety worry until now. for (c=1;c<8;c++) { // c (pre defined var) is left for // thread 0 use only. So, no worry. og[t]=c;bli=1;mm(t,"xtc"); // (o) & xm("tc") are adjusted for } // thread safety. (bli) is for Thread 0 use } // only. if (blpg[t]==1) { // All 7 threads share this block. for(int a=0;a<2;a++) { // (a) is defined here so, no worry. clsg[t]=" rgbcmpo".Substring(t,1)+"0"; // Form the color code for each thread osg[t]="Hello, this is thread number "+t+".";mm(t,"t"); } // (t) is unique for the thread, but } // (cls),(os) are common. So, method mm() } // uses t as is, but uses clsg[t],osg[t] } // in place of cls,os. =========================================================================================


TUTORIAL: The example has shown the basics necessary for writing a multi-thread program. The top 3 lines of method run() are common. They must be included in all multi-thread programs. They must be located above all block branching statements, so any thread, regardless of its starting block number executes them. As you have seen, each thread has introduced itself twice as wanted and used the exact color which we wanted it to use. So there has been no data corruption. However, the execution sequence is not the same as expected which is a feature we must live with when we work with multiple threads. However, if we have included the full code into one of the PC# thread safe methods m0():m9() we could have got the the expected sequence. Here is how it could have been done: ========================================================================================= public class a:pcs { public override void init() { o=8;dm("ht"); // Max threads=8 (including thread 0) tia=toa="t"; bli=0; base.init(); } public override void run() { //---- The following three lines must be included into any multi-thread program ----- int t; // Thread # Defined here so each thread Thread thp=Thread.CurrentThread; // will have its own unique copy. t=Int32.Parse(thp.Name); // Value of (t) for current thread //----------------------------------------------------------------------------------- if (blpg[t]==0) { os="Hello, this is thread number zero, I'll be creating 7 threads.";tm(); os="Each thread will be introducing itself twice.";tm(); // No thread safety worry until now. for (c=1;c<8;c++) { // c (pre defined var) is left for // thread 0 use only. So, no worry. og[t]=c;bli=1;mm(t,"xtc"); // (o) & xm("tc") are adjusted for } // thread safety. Threads can't use } // (bli) so only thread 0 uses it. if (blpg[t]==1) { // All 7 threads share this block. mm(t,"0"); // Access method m0() through method mm() } } public override void m0(int t) { // Method m0() requires thread number for(int a=0;a<2;a++) { // You write this code as if it was cls=" rgbcmpo".Substring(t,1)+"0"; // made for single thread operation os="Hello, this is thread number "+t+".";tm(); } } } =========================================================================================


Example 2: Let us now get into a more sophisticated application. Let us see how we can do graphics in a multi-thread environment. In example 4 of the drawing section, we have drawn the two sides of an "Ace of Diamonds" card Now we are going to try to use two threads to share the same job. One thread will draw the front side and the other thread will draw the back side. Based on what we have seen so far, all it takes to do this job should be the use of group var's and the thread safe method mm(). Let us see how it works. ========================================================================================= public class a:pcs { public override void init() { o=3;dm("ht"); // Allow the creation of upto 3 threads. bli=0; base.init(); } public override void run() { //------ We have contracted the same repeated 3 lines into one to save space ------- int t;Thread thp=Thread.CurrentThread;t=Int32.Parse(thp.Name); //---------------------------------------------------------------------------------- if (blp==0) { // Main thread (thread 0) start block og[t]=1;bli=1;mm(t,"xtc"); // Create thread #1 with starting block=1 og[t]=2;bli=2;mm(t,"xtc"); // Create thread #2 with starting block=2 } if (blp==1) { // Thread 1 starting block //----------------------------- Card's Front side -------------------------------- jfg[t]=-150;kfg[t]=0;lfg[t]=224;ofg[t]=300;mm(t,"gcrd"); clsg[t]="r0";mm(t,"gsps"); fnsg[t]="trp24"; osg[t]="A";jfg[t]=-242;kfg[t]=130;mm(t,"gctf"); osg[t]="A";jfg[t]=-58;kfg[t]=-130;adg[t]=180;mm(t,"gctf"); jfg[t]=-150;kfg[t]=0;lfg[t]=ofg[t]=30;adg[t]=45;mm(t,"gcrf"); jfg[t]=-242;kfg[t]=110;lfg[t]=ofg[t]=15;adg[t]=45;mm(t,"gcrf"); jfg[t]=-58;kfg[t]=-110;lfg[t]=ofg[t]=15;adg[t]=45;mm(t,"gcrf"); } if (blp==2) { // Thread 2 starting block //----------------------------- Card's Back side -------------------------------- clsg[t]="S9";mm(t,"gsps"); jfg[t]=+150;kfg[t]=0;lfg[t]=224;ofg[t]=300;mm(t,"gcrd"); clsg[t]="r0";ig[t]=5;mm(t,"gsps"); jfg[t]=150;kfg[t]=0;lfg[t]=209;ofg[t]=285;mm(t,"gcrd"); jfg[t]=150;kfg[t]=0;lfg[t]=199;ofg[t]=275;mm(t,"gcr"); clsg[t]="b0s9";adg[t]=-90;mm(t,"gspl"); mm(t,"ggrf"); JFG[t]=new float[]{150,50,250,150}; KFG[t]=new float[]{138,-138,-138,138}; oig[t]=4;mm(t,"gcp"); clsg[t]="b0s9";adg[t]=90;mm(t,"gspl"); mm(t,"ggrf"); } } } =========================================================================================


Look at what we got! Colors are messed up. So, what is the problem? The problem is common objects. One thread created a red solid brush and before it has been used, the other thread created a gradient blue-white paint brush. So colors have got mixed up. Now, how can we solve this problem? There are plenty of objects involved with graphics and they cost plenty of resources. Creating variable groups which allow each thread to use its own unique variables for supplying data to all methods and getting all returned data from them has not been too costy since numeric, boolean and string var's don't consume plenty of resources, but objects do. One answer is using methods m0():m9() They fix any common resource problem. So they should fix the common objects problem. So let us use m0() to draw the front side of the card and method m1() to draw the back side. ========================================================================================= public class a:pcs { public override void init() { o=3;dm("ht"); // Allow the creation of upto 3 threads. bli=0; base.init(); } public override void run() { int t;Thread thp=Thread.CurrentThread;t=Int32.Parse(thp.Name); if (blp==0) { // Main thread (thread 0) start block og[t]=1;bli=1;mm(t,"xtc"); // Create thread #1 with starting block=1 og[t]=2;bli=2;mm(t,"xtc"); // Create thread #2 with starting block=2 } if (blp==1) { // Thread 1 starting block mm(t,"0"); // Call m0() through the thread safemethod mm() } if (blp==2) { // Thread 2 starting block mm(t,"1"); // Call m1() through the thread safemethod mm() } } //---- Note that the methodes use the same code which has been used with single thread. public override void m0(int t) { //----------------------------- Card's Front side -------------------------------- jf=-150;kf=0;lf=224;of=300;gm("crd"); // Draw card outline rectangle cls="r0";gm("sps"); // Set color to pure red, solid pen/brush fns="trp24"; // Set font:TimesRoman, plain size=24 os="A";jf=-242;kf=130;gm("ctf"); // Draw the text "A" at top left os="A";jf=-58;kf=-130;ad=180;gm("ctf"); // Draw same rotated 180 degrees // at bottom right corner. jf=-150;kf=0;lf=of=30;ad=45;gm("crf"); // Fill a 30X30 square at card's center // rotated 45 degrees. jf=-242;kf=110;lf=of=15;ad=45;gm("crf");// Same but smaller at top left corner jf=-58;kf=-110;lf=of=15;ad=45;gm("crf");// and at bottom right corner } public override void m1(int t) { //----------------------------- Card's Back side -------------------------------- cls="S9";gm("sps"); // set color back to black solid jf=+150;kf=0;lf=224;of=300;gm("crd"); // Draw card outline rectangle cls="r0";i=5;gm("sps"); // Set pen color to red, size=5, solid jf=150;kf=0;lf=209;of=285;gm("crd"); // Draw a rectangular frame. jf=150;kf=0;lf=199;of=275;gm("cr"); // Create inner rectangle without drawing cls="b0s9";ad=-90;gm("spl"); // Set color to linear gradient changing // from blue to white at an angle of 90 deg // covering (gpp)'s bounding area. gm("grf"); // Render (gpp) and fill with color JF=new float[]{150,50,250,150}; // Create Gen Path with points defined in KF=new float[]{138,-138,-138,138}; // JF[],KF[], Point Count=4. Don't draw it. oi=4;gm("cp"); cls="b0s9";ad=90;gm("spl"); // Create linear gradient color covering the // new (gpp) object and changing colors at // opposite direction gm("grf"); // Render (gpp) and fill with color } } =========================================================================================


Wow. It worked great. These methods are very easy to write and seem to do the job very well. At least this is one way to look at them. But, this is not all. The reason they work so good is that when the first thread started, it called method mm() As you know method mm() allows only one thread to access it at a time. So the second thread had to wait until method mm() executed method m0() in full and finished the job for thread 1 before it was allowed to get in. This means that the two card sides have not been drawn simultaneously which could have been why we needed to use threads. So, we now need to look for a way which can make the two threads work on their drawings at the same time without causing data corruption. The only way to accomplish this is to use the same variable grouping method. However in the case of objects, we are going to let your program tell us exactly which objects are necessary for each thread in order to waste no resources. Method xm() at mode "tc" which creates all threads expects you to supply it with a string assigned to (os) which is made of the names of all necessary objects for the thread you want to create seperated with commas. Here is a list of all objects which can be privatized: gpp, bip, utp, spp, sbp, lgp, rgp, tbp, dtp, tcp, rqp, rsp, nsp They represent (in order): GraphicsPath, Bitmap, Matrix, pen, SolidBrush, LinearGradientBrush, PathGradientBrush, TextureBrush, DateTime, TcpClient, WebRequest, HttpWebResponse, NetworkStream Now let us see which objects are necessary for each of the two threads. Thread 1 creates rectangles using gm("cr") and paints them with solid paint pens and brushes only. So it requires the present GraphicsPath object (gpp), the solid pen (spp) and the solid brush (sbp) Thread 2 does the same tasks, and additionally creates Linear Gradient paint which requires object (lgp) So, thread1 uses gpp,spp,sbp And thread2 uses gpp,spp,sbp,lgp The first 3 objects are common so each thread should ask for its own private copies of them in order to avoid data corruption. The fourth object (lgp) is used by one thread only. So we cannot expect it to cause corruption. So there is no need to privatize it. Now let us see how to modify the code to allow for private gpp,spp,sbp. ========================================================================================= public class a:pcs { public override void init() { o=3;dm("ht"); bli=0; base.init(); } public override void run() { int t;Thread thp=Thread.CurrentThread;t=Int32.Parse(thp.Name); if (blp==0) { og[t]=1;osg[t]="gpp,spp,sbp";bli=1;mm(t,"xtc"); // Here is the new change og[t]=2;osg[t]="gpp,spp,sbp";bli=2;mm(t,"xtc"); // } // --------------- Everything from here down has not been changed ---------------- if (blp==1) { // Thread 1 starting block //----------------------------- Card's Front side -------------------------------- jfg[t]=-150;kfg[t]=0;lfg[t]=224;ofg[t]=300;mm(t,"gcrd"); clsg[t]="r0";mm(t,"gsps"); fnsg[t]="trp24"; osg[t]="A";jfg[t]=-242;kfg[t]=130;mm(t,"gctf"); osg[t]="A";jfg[t]=-58;kfg[t]=-130;adg[t]=180;mm(t,"gctf"); jfg[t]=-150;kfg[t]=0;lfg[t]=ofg[t]=30;adg[t]=45;mm(t,"gcrf"); jfg[t]=-242;kfg[t]=110;lfg[t]=ofg[t]=15;adg[t]=45;mm(t,"gcrf"); jfg[t]=-58;kfg[t]=-110;lfg[t]=ofg[t]=15;adg[t]=45;mm(t,"gcrf"); } if (blp==2) { // Thread 2 starting block //----------------------------- Card's Back side -------------------------------- clsg[t]="S9";mm(t,"gsps"); jfg[t]=+150;kfg[t]=0;lfg[t]=224;ofg[t]=300;mm(t,"gcrd"); clsg[t]="r0";ig[t]=5;mm(t,"gsps"); jfg[t]=150;kfg[t]=0;lfg[t]=209;ofg[t]=285;mm(t,"gcrd"); jfg[t]=150;kfg[t]=0;lfg[t]=199;ofg[t]=275;mm(t,"gcr"); clsg[t]="b0s9";adg[t]=-90;mm(t,"gspl"); mm(t,"ggrf"); JFG[t]=new float[]{150,50,250,150}; KFG[t]=new float[]{138,-138,-138,138}; oig[t]=4;mm(t,"gcp"); clsg[t]="b0s9";adg[t]=90;mm(t,"gspl"); mm(t,"ggrf"); } } } =========================================================================================


Now we have accomplished both objectives. The two sides have been drawn simultaneously and data corruption has been eliminated. ========================================================================================= INTRA-THREAD COMMUNICATION In order to give threads the ability to share projects together, PC# allows threads to send and receive messages to/from each other. Each thread is assigned one mail box which can store only one message string. A message string is made of the sender thread number, followed with a colon then followed with the message. The mail box owner thread can receive its message by calling method xm("tmr"). This method obtains the sender thread number and the message which it returns to the owner thread assigned to (o), (os) respectively. Then it erases the message string so that the mail box can receive a new message. If the message was received successfully, (dnb=true) is also returned to the owner thread together with (o) and (os). If no message was found in the box (dnb=false) is returned. Messages are sent by calling method xm("tms"). If the message is sent successfully, (dnb=true) will be returned to the sender thread. If the message could not be sent because the target mail box was full, (dnb=false) is returned. Here is the actual code for receiving an expected message: dnbg[t]=false; // Initialize the "job done flag" while (!dnbg[t]) { // Start a loop which terminates when message is received mm(t,"xtmr"); // Look for a message, if found receive it. Thread.Sleep(10); // Wait 10 milliseconds then repeat loop. } And here is the actual code for sending a message: // Assign the rcepient thread number to (o) and the message to (os) before the loop dnbg[t]=false; // Initialize the "job done flag" while (!dnbg[t]) { // Start a loop which terminates when message is sent mm(t,"xtms"); // Check recepient mail box. If empty send message there. Thread.Sleep(10); // Wait 10 milliseconds then repeat loop. } The two routines above for receiving and sending messages are general and you'll need to use them over and over in your program each time you like a thread to receive or send a message. We have contracted each of them in a single line to reduce the space they occupy. You can use "copy-paste" to insert each of them into your code. dnbg[t]=false;while (!dnbg[t]) {mm(t,"xtmr");Thread.Sleep(10);}// Recv msg dnbg[t]=false;while (!dnbg[t]) {mm(t,"xtms");Thread.Sleep(10);}// Send msg ========================================================================================= Example 3: This Example shows how threads can work together to perform a task. The main thread is going to create 3 threads then manage them to share the computation of the equation y = x^2 + 1. Thread 1 will generate a random number and assign it to (x). Thread 2 will calculate (x^2) and thread 3 will add 1, coming up with the value of (y). ========================================================================================= public class a:pcs { public override void init() { toa="t"; o=4;dm("ht"); // Allow the creation of upto 4 threads. bli=0; base.init(); } public override void run() { int t;Thread thp=Thread.CurrentThread;t=Int32.Parse(thp.Name); if (blpg[t]==0) { // Block 0 is for manager thread cls="r0"; os="Thread 0 creates 3 threads 1,2 and 3. The 3 threads share the calculation";tm(); os="of the equation 'y = x^2 + 1'. Thread 0 manages the operation through";tm(); os="internal messaging.";tm(); os="";tm(); for (c=1;c<4;c++) { // Thrd 0 uses (c) & all pre-defined var's alone og[t]=c;bli=1;mm(t,"xtc"); // Create 3 threads, all start at blc 1. } for (int c=1;c<4;c++) { // Scan all 3 threads og[t]=c;osg[t]="Thread "+c+", Do your part.";// Send this msg to each of them dnbg[t]=false;while (!dnbg[t]) {mm(t,"xtms");Thread.Sleep(10);}// Send msg clsg[t]="b0";osg[t]="Sent to Thread "+c+": "+osg[t];mm(t,"t"); // Also display the same message // Then attempt to receive reply dnbg[t]=false;while (!dnbg[t]) {mm(t,"xtmr");Thread.Sleep(10);}// Recv msg clsg[t]="S9";osg[t]="Received from thread "+og[t]+": "+osg[t];mm(t,"t"); osg[t]="";mm(t,"t"); // Display reply, skip one line. } } else if (blpg[t]==1) { // Block 1 is for all other threads dnbg[t]=false;while (!dnbg[t]) {mm(t,"xtmr");Thread.Sleep(10);}// Try to Recv msg // If msg received do the following: if (t==1) { // If this is 1st thread ig[t]=100;mm(t,"umr"); // get a random number in (o), mm(t,"ofi");mm(t,"otd"); // Then convert it to double nd=odg[t]; // and assign it to (nd) osg[t]="Thread "+t+" reporting: The random number "+nd+" has been assigned to x"; } // Prepare msg to be sent to manager else if (t==2) { // If this is 2nd thread jdg[t]=2;odg[t]=nd;mm(t,"ump"); // Calculate (nd^2) using (od) to nd=odg[t]; // perform the math osg[t]="Thread "+t+" reporting: Result of squaring operation x^2="+nd; } // Prepare msg to be sent to manager else if (t==3) { // If this is 3rd thread nd++; // Increment(nd) osg[t]="Thread "+t+" reporting: Equation complete. Result (x^2+1)="+nd; } // Prepare msg to be sent to manager og[t]=0; // Msg recipient thread is 0 dnbg[t]=false;while (!dnbg[t]) {mm(t,"xtms");Thread.Sleep(10);}// Send msg og[t]=t;mm(t,"xta"); // Then abort } } } =========================================================================================


========================================================================================= HANDLING FILES IN A MULTI-THREAD ENVIRONMENT Problems with files are not the same as problems with graphics. With files you don't need to worry about objects. PC# creates the stream objects necessary for each file you open and keeps them available until you close the file. Any thread can open a file and any other thread can read or write data to that file as long as it assigns the file keyname to (fs) before it calls the filing method fm(). So files are common for all threads and this creates a problem. You can use private variables and use the thread safe method mm() when you read or write to a file, but this will not correct all problems. Let us suppose that you wrote a program in which each thread reads data from a file, modifies it then writes it back into the same file. You must make sure that each thread completes all three operations as a package before any other thread gets involved. If one thread reads the data then another thread reads the same data before it was modified by the first one, the final result will not be the same. let us have an example. We'll see what happens if we do the 3 operations as a package using method m0() and what happens if we do them individually. ========================================================================================= Example 4: The main thread will create a "no header" Random Access File, open it and write "1" into record number 0. Then it will create 6 threads. Each thread will read the same data, modifies it then writes it back. The even numbered threads will modify the data by doubling its value and the odd numbered threads will modify the data by deviding its value by 2. After all threads have finished, the main thread will read the ending data, display it then close the file. If everything works fine we should expect the ending data to be 1. Let us try doing the three operations as a package first. ========================================================================================= public class a:pcs { public override void init() { o=7;dm("ht"); // Max threads=7 (including thread 0) toa="t"; bli=0; base.init(); } public override void run() { int t;Thread thp=Thread.CurrentThread;t=Int32.Parse(thp.Name); if (blpg[t]==0) { // Block 0 is for main thread fls="test.raf";fm("A"); // check file attributes if(os != " ") {ks="f";fm("D");} // If exists, delete it fls="test.raf";fs="rf0";rcl=10;ib=true;fm("o");// open RAF, record len=10,no header rcs=""+1;rci=0;fm("w"); // Write "1" into record # 0 os="Starting data:"+1;tm(); // Display starting data os="";tm(); // Skip one line for (c=1;c<7;c++) { // Create 6 threads All start og[t]=c;bli=1;mm(t,"xtc"); // at block 1. } dnbg[t]=false; // Initialize flag (Reset it) while (!dnbg[t]) { // Start a loop which ends when all dnbg[t]=true; // threads finish their jobs.Set flag for (int c=1;c<7;c++) { // Scan all 6 threads og[t]=c;mm(t,"xts"); // Check state.If any neither stopped if ("sa".IndexOf(osg[t])<0) dnbg[t]=false; // nor aborted reset flag back. Thread.Sleep(10); // Pause to allow other threads share } } fs="rf0";rci=0;fm("r"); // Read ending data. os="";tm(); // Skip one line os=rcs;om("c"); // Clean data os="Ending data="+os;tm(); // Display ending data fm("c"); // Close file } else if (blpg[t]==1) { // Block 1 is for all created threads mm(t,"0"); // Execute m0() } } public override void m0(int t) { fs="rf0";rci=0;fm("r"); // Read data from file os=rcs;om("c"); // Clean data os="Thread number:"+t+" Data read:"+os;tm(); // Display read data os=rcs;om("td"); // Convert string data to double if (t%2==0) od=od*2; // If Thread # even, multiply by 2 else od=od/2; // Else divide by 2 os="Thread number:"+t+" Data written:"+od;tm(); // Display data to be written rcs=""+od;rci=0;fm("w"); // Write data into file } } =========================================================================================


Everthing worked perfectly. Now let us try to do them individually while insuring thread safety. Replace block 1 with: else if (blpg[t]==1) { // Block 1 is for all created threads fsg[t]="rf0";rcig[t]=0;mm(t,"fr"); // Read data from file osg[t]=rcsg[t];mm(t,"oc"); // Clean data to be displayed osg[t]="Thread number:"+t+" Data read:"+osg[t];mm(t,"t"); // Display read data osg[t]=rcsg[t]; mm(t,"otd"); // Convert data to double if (t%2==0) odg[t]=odg[t]*2; // If Thread # even, multiply by 2 else odg[t]=odg[t]/2; // Else divide by 2 osg[t]="Thread number:"+t+" Data written:"+odg[t];mm(t,"t"); // Display data to be written rcsg[t]=""+odg[t];rcig[t]=0;mm(t,"fw"); // Write data into file } =========================================================================================


As expected we have ended with wrong results caused by filing corruption. ========================================================================================= TUTORIAL: Here are the rules which you need to know about threads: (1) The top line in method run, before block branching must always be: int t;thp=Thread.CurrentThread;t=Int32.Parse(thp.Name); (2) If you need to declare new thread-safe variables, make your declaration in method run. Each thread has its own private copy of all variables which are declared within method run() while class top declared variables are common for all threads. (3) The pre-declared variables which are assigned to your program are not thread safe. Therefore we suggest using them in one of the following manners: a) Limit their use to the main thread only (thread 0) b) Allow all threads to use them, but don't allow two threads to use the same one. c) Allow all threads to share them, but make sure to lock the object while used. (4) Whenever threads must use and modify a common resource like a file, write your code into one of the 10 methods [m0():m9()]. Your code should not use thread-safe group variables or method mm() Everything must be written as if it was made for single thread use. Threads should not call these methods directly. They should be accessed through the thread-safe method mm(). (5) The block request variable (bli) is treated like one of the pre-declared variables. It should be used by thread 0 only. All other threads are limited to one block so they have no use for (bli). However (blp) should be used through var groups. All block branching statements should be similar to this: if (blpg[t]==n) {...} Where (n) is the block number. (6) When more than one thread execute a common loop, make sure to include a "sleep" statement of (10 to 100) milli-seconds within the loop to insure that all threads get a fair share. (7) DO NOT use PC# methods to sleep the thread in a multi-thread environment. To sleep the thread for n milli-seconds use the code: Thread.Sleep(n); If you like another thread to be able to wake it up before the time has elapsed, use this code instead: try {Thread.Sleep(n);} catch(ThreadInterruptedException e) {exp=e;} The thread which wakes it up should use the thread-safe method mm() to interrupt the sleep process as follows: og[t]=a;mm(t,"xti"); Where a=Number of thread to be awaken. =========================================================================================


ASYNCHRONOUS METHODS: ===================== Sometimes when the main thread needs to perform an operation which requires an unpredictable amount of time, it would be of benefit to do the operation asynchronously. The ".NET" software contains a useful but somewhat complicated way to execute a method asynchronously. Fortunately we have all the tools to do this job in a simple manner. All we need to do is to create a thread, let it do the job then send a message with the result to the main thread when its job is done. The main thread does not have to wait for the result. It can do whatever it needs to do and when it's ready, checkes its mail box to get the result of the operation. Example 3 has shown how three threads can work together to perform a common project with the management of the main thread. This is not exactly what we need here. The requirements are different. In example 3, the main thread's task was to be part of a thread team. Here, the main thread has a programming project to accomplish. It needs to do a side job asynchronously, but it cannot allow this to complicate its main job. For example, it does not like to access every method through the thread-safe method mm() or to lock objects before each operation. Let us see how this can be done. How could data corruption be avoided while the main thread is using PC# methods directly? ----------------------------------------------------------------------------------------- Normally, if a created thread accesses PC# methods through method mm(), the main thread must also use method mm() to access PC# methods in order to avoid data corruption. With version 1.6 this rule is still valid but with one exeption. If the created thread's call has been for method xm() to perform an operation on threads, the call will be executed seperately in a manner which will shield the main thread. This means that an asynch method can send its return value to the main thread using method mm(t,"xtms") without causing a problem unless it accesses other PC# methods. So there are two options for writing the asynch method's code; either to make it all without using PC# methods except for sending the metod's return value, or to create a private instance of class (pcs) to be used by the asynch method alone. In the next example, we'll be doing just this. Remarks: -------- The block "0" conditional statement "if (blpg[t]==0)" must use group var's. Not because thread "0" requires it, but because the created thread must pass by this statement while in its way to its assigned block. Other than that we like to try to make thread "0" use single thread statements for all its operations. Thread "0" owns two sets of variables. The single thread var's like (os) and its share in group var's like (osg[0]) It will be using the group var's to communicate with the asynchronous method and the single thread var's for everything else. About the next example: ----------------------- The first example in "Networking" shows how to obtain the latest stock price for a stock symbol using Yahoo's free quotes web page. In the next example we are going to show how to do this job asynchronously. The main thread will create a thread and assign a stock symbol to one of its private var's. The main thread will continue its main task. It will create a graphic container which will be used to display the stock price into. The new thread will obtain the stock price and send it in a message to the main thread. The main thread will receive the stock price and display it. =============================================================================================== Example 5: This Example shows how the main thread can launch an asynchronous method to obtain the stock price for a stock symbol and send the result in a message. The main thread will then create a graphical container to display the received stock price into. =============================================================================================== public class a:pcs { public override void init() { o=2;dm("ht"); // Allow the creation of upto 2 threads. bli=0; // Main thread will start at block 0. base.init(); } public override void run() { int t;Thread thp=Thread.CurrentThread;t=Int32.Parse(thp.Name); if (blpg[t]==0) { // Block 0 is for main thread cls="r0";gm("sps"); // Display title. os="USING AN ASYNCHRONOUS METHOD TO OBTAIN A STOCK PRICE";kf=160;gm("ctf"); //---------------------------- Starting the asynch method ------------------------------- jsg[1]="yhoo"; // Assign Yahoo's symbol to (js) of the new thread o=1;bli=1;xm("tc"); // Create new thread starting at block 1. //-------------------------- Creating the graphical container --------------------------- lf=80;of=40;kf=25;gm("cr"); // Create inner rectangle. gm("gc-"); // Make it negative clip area. cls="b0";gm("sps"); // Create solid blue prush lf=200;of=200;gm("cef"); // Draw-fill a circle cls="s9";gm("sps");fns="trb16"; // Change to white color and different font os="Yahoo Corporation";kf=-25;gm("ctf"); // Draw string slightly above center gm("gcn"); // Cancel clip area //----------------------- Receiving and displaying the stock price ---------------------- // This line is copied from example 3 (as explained before) dnb=false;while (!dnb) {xm("tmr");Thread.Sleep(10);}// Recv msg (assigned to os) cls="S9";gm("sps");fns="trb20"; // Change color to black. Also change font kf=25;gm("ctf"); // Draw the message's text (The stock price) } //------------------------------- Obtaining the stock price ------------------------------- else if (blpg[t]==1) { // Block 1 is for the created thread. pcs p=new pcs(); // Create an instance of class (pcs) jsg[t]=jsg[t].ToUpper(); p.urs="http://money.cnn.com/quote/quote.html?symb="+jsg[t]; p.jb=true;p.kb=true;p.nm("hg"); //Get html file replacing {}<> with |'s p.txs=p.os; // Assign file to search string (txs) p.js=":"+jsg[t]+")";p.ks="As of";p.tm("s"); // Get the string between js & ks and assign it to (os) for(p.c=p.os.Length-1;p.c>-1;p.c--) { // Scan (os) char's backward. Stop at first numeric char if("0123456789.,".IndexOf(p.os.Substring(p.c,1))>-1) break; } p.os=p.os.Substring(0,p.c+1); // Remove all chars after that one from (os) for(p.c=p.os.Length-1;p.c>-1;p.c--) { // Scan (os) char's again. Stop at first char which if("0123456789.,".IndexOf(p.os.Substring(p.c,1))<0) break; } // is not a digit, decimal point or comma. p.os=p.os.Substring(p.c+1); // Remove all char's upto that one from (os) //------------------------ Sending the stock price to main thread ----------------------- osg[t]=p.os;og[t]=0; // Message to send to thread 0 (The stock price) // This line is copied from example 3 (as explained before) dnbg[t]=false;while (!dnbg[t]) {mm(t,"xtms");Thread.Sleep(10);}// Send msg } } } ===============================================================================================


============================================================================================== Setting Timout for message reception: ------------------------------------- When the main thread received the asynch method's returned stock price, it entered into an endless loop which ended only when a message was received. Sometimes this can be risky since the thread may never be able to get the price or send the message. This problem can be solved by setting a timeout for the receive operation. The original code for the receive operation was: dnb=false;while (!dnb) {xm("tmr");Thread.Sleep(10);} If you like to set a timeout of 3 seconds for the operation, modify code as follows: dnb=false; // Initialize the "message found" flag ib=true;um("t"); // Start timing while (!dnb) { // Continue next operation until a message is found. um("t"); // Get the amount of time passed if (o>3000) { // If found to exceed timeout os="T/O";break; // Return "T/O" (Instead of stock price), exit loop. } xm("tmr");Thread.Sleep(10); // Attempt receiving the message, pause 10 ms. } // Repeat. Catching Exceptions within an asynch method: -------------------------------------------- When you write this asynch method in a real world project, you know that the method could encounter Exceptions. The Exceptions must be caught and either sent in a message to the main thread or logged into the event log. Sorry, writing into event log has not been discussed yet, it will be discussed when we get into "Performing System Operations". However, since it's very simple, we are going to show you here how to throw an exception within the asynch method and how to catch it and log its message into the event log. We are going to modify "block 1" by setting its code into a try block, throw a "null" exception which normally happens when we try to operate on a null object, then catch the exception and send its message to the "Application" event log. //------------------------- Obtaining the stock price -------------------------- else if (blpg[t]==1) { // Block 1 is for the created thread. pcs p=new pcs(); // Create an instance of class (pcs) try { // Start of the try block // ************************* No changes made in this section *************************** jsg[t]=jsg[t].ToUpper(); p.urs="http://money.cnn.com/quote/quote.html?symb="+jsg[t]; p.jb=true;p.kb=true;p.nm("hg"); //Get html file replacing {}<> with |'s p.txs=p.os; // Assign file to search string (txs) p.js=":"+jsg[t]+")";p.ks="As of";p.tm("s"); // Get the string between js & ks & assign it to (os) for(p.c=p.os.Length-1;p.c>-1;p.c--) { // Scan (os) char's backward. Stop at 1st numeric chr if("0123456789.,".IndexOf(p.os.Substring(p.c,1))>-1) break; } p.os=p.os.Substring(0,p.c+1); // Remove all chars after that one from (os) for(p.c=p.os.Length-1;p.c>-1;p.c--) { // Scan (os) char's again. Stop at first char which if("0123456789.,".IndexOf(p.os.Substring(p.c,1))<0) break; } // is not a digit, decimal point or comma. p.os=p.os.Substring(p.c+1); // Remove all char's upto that one from (os) //------------------------ Sending the stock price to main thread ----------------------- osg[t]=p.os;og[t]=0; // Message to send to thread 0 (The stock price) // This line is copied from example 3 (as explained before) dnbg[t]=false;while (!dnbg[t]) {mm(t,"xtms");Thread.Sleep(10);}// Send msg // ************************************************************************************** throw null; // Throw a null exception } // End of the try block. catch (Exception e) { // Start of the catch block p.js="Application";p.ks="a.exe, thread "+t; // Use Application log, ks= Event Source p.oc='e';p.os=e.Message; // Event type="Error", os=Message to be logged. p.sm("lw");p.sm("e"); // Write to event log then exit } // End of the catch block. } Now, Click on [start][Control Panel], double click [Administrative tools] then [Event Viewer]. To view the "Application" log, double click its name on the left panel. You should see the event log table. Notice the newly added row which says that the event type is "Error" and the event source is "a.exe, thread 1". If you double click anywhere on that row, you'll see the error message which has been logged which is: "Object reference not set to an instance of an object." ============================================================================================== USING MULTIPLE ASYNCHRONOUS METHODS: ==================================== Our asynchronous methods are easy to create and are guaranteed to run perfectly since data corruption is impossible to happen. In order to give our asynch methods a harder stress test, we are going to create three threads this time, each one will get the stock price for a different company. The main thread will follow the creation of the three threads with creating three graphical containers to display the stock prices into. As you know, you can't guarantee that the thread which has been created the first will be the one which finishs its job the first. Fortunately messages between threads contain the sender thread number which we can use to identify the stock symbol which the received price is for. ============================================================================================== Example 6: Repeat example 5 using three asynchronous methods to get the stock price for three different stock symbols. ============================================================================================== public class a:pcs { public override void init() { o=4;dm("ht"); // Allow the creation of upto 4 threads. bli=0; base.init(); } public override void run() { int t;Thread thp=Thread.CurrentThread;t=Int32.Parse(thp.Name); if (blpg[t]==0) { // Main thread starting block cls="r0";gm("sps"); // Display title. os="USING MULTIPLE ASYNCHRONOUS METHODS";kf=160;gm("ctf"); //-------------------------------- Creating the threads ----------------------------- jsg[1]="yhoo";o=1;bli=1;xm("tc"); // Create 3 threads, all start jsg[2]="msft";o=2;bli=1;xm("tc"); // at block 1. The private var (js) of jsg[3]="intc";o=3;bli=1;xm("tc"); // each thread is assigned a stock symbol. for (n=1;n<4;n++) { // For all created threads, i=1;o=n;xm("tp"); // Set priority down to smooth-up operation. } i=4;o=0;xm("tp"); // For thread "0", Set priority up. //------------------------ Preparing the graphical containers ----------------------- for (c=0;c<3;c++) { // Repeat the following 3 times: lf=80;of=40;jf=c*240-240;kf=25;gm("cr"); // Create the inner rectangle gm("gc-"); // Make it a negative clip area cls="b0 o0 r0".Substring (c*3,2);gm("sps"); // Create brush with one of 3 colors lf=200;of=200;jf=c*240-240;gm("cef"); // Draw-fill a circle at one of 3 loc's cls="s9";gm("sps");fns="trb16"; // Change color to white. Change font. if (c==0) {os="Yahoo Corporation";jf=-240;} // Set the text to draw and else if (c==1) {os="Microsoft Corporation";jf=0;}// its location into else if (c==2) {os="Intel Corporation";jf=240;} // the form. kf=-25;gm("ctf"); // Draw the text gm("gcn"); // Reset clip area. } //---------------------- Receive and display the stock prices ----------------------- cls="S9";gm("sps");fns="trb20"; // Prepare color and font. for (c=0;c<3;c++) { // Repeat the following 3 times: dnb=false;while (!dnb) {xm("tmr");Thread.Sleep(10);}// Recv msg if(o==1) jf=-240; // If came from thread 1, set it into left circle else if(o==2) jf=0; // If from thread 2, set it into center circle else if(o==3) jf=240; // If from thread 3, set it into right circle kf=25;gm("ctf"); // Display the stock price. } } //---------------------------- The Asynchronous methods ------------------------------ else if (blpg[t]==1) { // Block 1 is for all created threads pcs p=new pcs(); // Create instance of (pcs) for each thr. jsg[t]=jsg[t].ToUpper(); p.urs="http://money.cnn.com/quote/quote.html?symb="+jsg[t]; p.jb=true;p.kb=true;p.nm("hg"); //Get html file replacing {}<> with |'s p.txs=p.os; // Assign file to search string (txs) p.js=":"+jsg[t]+")";p.ks="As of";p.tm("s"); // Get the string between js & ks. Assign it to (os) for(p.c=p.os.Length-1;p.c>-1;p.c--) { // Scan (os) char's backward. Stop at 1st numeric chr if("0123456789.,".IndexOf(p.os.Substring(p.c,1))>-1) break; } p.os=p.os.Substring(0,p.c+1); // Remove all chars after that one from (os) for(p.c=p.os.Length-1;p.c>-1;p.c--) { // Scan (os) char's again. Stop at first char which if("0123456789.,".IndexOf(p.os.Substring(p.c,1))<0) break; } // is not a digit, decimal point or comma. p.os=p.os.Substring(p.c+1); // Remove all char's upto that one from (os) //-------------------- Sending stock prices to main thread ------------------------- osg[t]=p.os; // Assign the stock price to private (os) og[t]=0; // Send message to thread "0". dnbg[t]=false;while (!dnbg[t]) {mm(t,"xtms");Thread.Sleep(10);}// Send msg } } } ==============================================================================================