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 zero, 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 a message 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.
=========================================================================================
BEST SELLERS FROM AMAZON.COM
Books, C Sharp Books, .NET Computers Electronics Industrial & Scientific Items MP3 Downloads DVD Camera & Photo Cell Phones & Services Magazine Subscriptions Office Products On Demand Videos
|
 |
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
}
}
}
=========================================================================================
BEST SELLERS FROM AMAZON.COM
Books, C Sharp Books, .NET Computers Electronics Industrial & Scientific Items MP3 Downloads DVD Camera & Photo Cell Phones & Services Magazine Subscriptions Office Products On Demand Videos
|
 |
=========================================================================================
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="esb26"; // 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)
p.urs="http://finance.yahoo.com/q?d=s&s="+jsg[t];
// Yahoo's URL to get the stock price
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="Last Trade:";p.ks="Trade Time:";p.tm("s");
// Get data between js,ks in (os)
char[] CC=p.os.ToCharArray(); // Convert (os) to Char array to scan it
p.os=""; // Reset (os)
for (int n=0;n< CC.Length;n++) { // Scan all char's
if (CC[n]!='|') p.os+=CC[n]; // Add all chars to (os) except |'s
}
//------------------------ 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 ***************************
p.urs="http://finance.yahoo.com/q?d=s&s="+jsg[t];
// Yahoo's URL to get the stock price
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="Last Trade:";p.ks="Trade Time:";p.tm("s");
// Get data between js,ks in (os)
char[] CC=p.os.ToCharArray(); // Convert (os) to Char array to scan it
p.os=""; // Reset (os)
for (int n=0;n< CC.Length;n++) { // Scan all char's
if (CC[n]!='|') p.os+=CC[n]; // Add all chars to (os) except |'s
}
//---------------- 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="esb26"; // 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.
p.urs="http://finance.yahoo.com/q?d=s&s="+jsg[t];// Yahoo's URL for getting stock price
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="Last Trade:";p.ks="Trade Time:";p.tm("s"); // Get data between js,ks in (os)
char[] CC=p.os.ToCharArray(); // Convert (os) to Char array to scan it
p.os=""; // Reset (os)
for (int n=0;n< CC.Length;n++) { // Scan all char's
if (CC[n]!='|') p.os+=CC[n]; // Add all chars to (os) except |'s
}
//-------------------- 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
}
}
}
==============================================================================================
BEST SELLERS FROM AMAZON.COM
Books, C Sharp Books, .NET Computers Electronics Industrial & Scientific Items MP3 Downloads DVD Camera & Photo Cell Phones & Services Magazine Subscriptions Office Products On Demand Videos
|
 |
|