9.1 Processes
A process in its simplest form is a running application. The Process class provides
access to local and remote processes and enables us to start and stop local system
processes.
The Process.Start() method starts a process resource by specifying the name of
a document or application file and associates the resource with a new process component.
Thus we can start other applications from within our application.
The program below launches notepad.exe, winhlp32.exe and asynchronously counts
up from 0 to 100001, then destroys the notepad.exe process using the Kill()
method. It counts again from 0 to 2000 and then kills the winhlp32.exe process.
Each process has an Id number.
// process1.cs
using System;
using System.Diagnostics;
class Processtest
{
public static void Main()
{
Process p = Process.Start("notepad.exe");
string name = p.ProcessName;
int id = p.Id;
DateTime started = p.StartTime;
Console.WriteLine("name = " + name);
Console.WriteLine("id = " + id);
Console.WriteLine("StartTime = " + started);
114
9.2. THREADS 115
Process w = Process.Start("winhlp32.exe");
for(int i=0;i<100001;i++) Console.WriteLine(i);
p.Kill();
for(int j=0;j<2000;j++) Console.WriteLine(j);
w.Kill();
}
}
If we also want to load a file (for example process2.cs) with notepad.exe we
change this line to
Process p = Process.Start("notepad.exe","c:\\csharp\\process2.cs");
The detect process completion we use the method
WaitForExit()
9.2 Threads
9.2.1 Introduction
A thread is an execution stream within a process. A thread is also called a lightweight
process. It has it own execution stack, local variables, and program counter. There
may be more than one thread in a process. Threads are part of the same process
which execute concurrently. In .NET Base Class Library (BCL) the System.Threading
namespace provides various classes for executing and controlling threads.
The following program Threads0.cs is the simple example. We create a Thread
instance by passing it an object of ThreadStart delegate which contains a reference
to our ThreadJob method. Thus the code creates a new thread which runs the
ThreadJob method, and starts it. That thread counts from 0 to 1 while the main
thread counts from 0 to 5.
// Threads0.cs
using System;
using System.Threading;
public class Threads0
{
static void Main()
{
ThreadStart job = new ThreadStart(ThreadJob);
Thread thread = new Thread(job);
116 CHAPTER 9. PROCESSES AND THREADS
thread.Start();
int i = 0;
while(i < 6)
{
Console.WriteLine("Main thread: {0}",i);
i++;
} // end while
} // end Main
static void ThreadJob()
{
int i = 0;
while(i < 2)
{
Console.WriteLine("Other thread: {0}",i);
i++;
} // end while
} // end ThreadJob
}
An output could be
Main thread: 0
Main thread: 1
Main thread: 2
Main thread: 3
Main thread: 4
Main thread: 5
Other thread: 0
Other thread: 1
At another run the output could be
Other thread: 0
Other thread: 1
Main thread: 0
Main thread: 1
Main thread: 2
Main thread: 3
Main thread: 4
Main thread: 5
9.2.2 Background Thread
In the program threadtest2.cs the Thread t is set to the background thread
9.2. THREADS 117
t.IsBackgound = true;
The thread will terminate when the application terminates.
// threadtest2.cs
using System;
using System.Threading; // for Thread class
class ThreadTest2
{
public static void SayHello()
{
for(int i=1;;i++)
{
Console.WriteLine("Hello {0}",i);
}
}
public static void Main()
{
Thread t = new Thread(new ThreadStart(SayHello));
t.IsBackground = true;
t.Start();
for(int i=1;i<1001;i++)
{
Console.WriteLine("Bye {0}",i);
}
}
}
9.2.3 Sleep Method
Once a thread has been started it is often useful for that thread to pause for a
fixed period of time. Calling Thread.Sleep causes the thread to immediately
block for a fixed number of milliseconds. The Sleep method takes as a parameter
a timeout, which is the number of milliseconds that the thread should remain
blocked. The Sleep method is called when a thread wants to put itself to
sleep. One thread cannot call Sleep on another thread. Calling Thread.Sleep(0)
causes a thread to yield the remainder of its timeslice to another thread. Calling
Thread.Sleep(Timeout.Infinite) causes a thread to sleep until it is interrupted
by another thread that calls Thread.Interrupt. A thread can also be paused
by calling Thread.Suspend. When a thread calls Thread.Suspend on itself, the
call blocks until the thread is resumed by another thread. When one thread calls
Thread.Suspend on another thread, the call is a non-blocking call that causes the
118 CHAPTER 9. PROCESSES AND THREADS
other thread to pause.
The following program Threads0.cs is the simple example. We create a Thread
instance by passing it an object of ThreadStart delegate which contains a reference
to our ThreadJob method. Thus the code creates a new thread which runs the
ThreadJob method, and starts it. That thread counts from 0 to 9 fairly fast (about
twice a second) while the main thread counts from 0 to 4 fairly slowly (about once a
second). The way they count at different speeds is by each of them including a call
to Thread.Sleep(), which just makes the current thread sleep (do nothing) for the
specified period of time (milliseconds). Between each count in the main thread we
sleep for 1000ms, and between each count in the other thread we sleep for 500ms.
// ThreadsSleep.cs
using System;
using System.Threading;
public class ThreadsSleep
{
static void Main()
{
ThreadStart job = new ThreadStart(ThreadJob);
Thread thread = new Thread(job);
thread.Start();
int i = 0;
while(i < 5)
{
Console.WriteLine("Main thread: {0}",i);
Thread.Sleep(1000);
i++;
} // end while
} // end Main
static void ThreadJob()
{
int i = 0;
while(i < 10)
{
Console.WriteLine("Other thread: {0}",i);
Thread.Sleep(500);
i++;
} // end while
} // end ThreadJob
}
9.2. THREADS 119
A typical output could be
Main thread: 0
Other thread: 0
Other thread: 1
Main thread: 1
Other thread: 2
Other thread: 3
Other thread: 4
Main thread: 2
Other thread: 5
Main thread: 3
Other thread: 6
Other thread: 7
Main thread: 4
Other thread: 8
Other thread: 9
At another run this could change.
9.2.4 Join Methods
The method Join() blocks the calling thread until a thread terminates.
// joiningthread.cs
using System;
using System.Threading;
class JoiningThread
{
public static void Run()
{
for(int i=1;i<3;i++)
Console.WriteLine("Hello {0}",i);
}
public static void Main()
{
Thread t = new Thread(new ThreadStart(Run));
t.Start();
for(int i=1;i<6;i++)
Console.WriteLine("Welcome {0}",i);
t.Join();
Console.WriteLine("Goodbye");
}
120 CHAPTER 9. PROCESSES AND THREADS
}
The output is
Welcome 1
Welcome 2
Welcome 3
Welcome 4
Welcome 5
Hello 1
Hello 2
9.3 Monitor
While one thread is in a read/increment/write operation, no other threads can try
to do the same thing. This is where monitors come in. Every object in .NET has
a monitor associated with it. A thread can enter (or acquire) a monitor only if no
other thread has currently “got” it. Once a thread has acquired a monitor, it can
acquire it more times, or exit (or release) it. The monitor is only available to other
threads again once it has exited as many times as it was entered. If a thread tries
to acquire a monitor which is owened by another thread, it will block until it is able
to acquire it. There may be more than one thread trying to acquire the monitor,
in which case when the current owner thread releases it for the last time, only one
of the threads will acquire it - the other one will have to wait for the new owner to
release it too. The Pulse(object) method notifies a thread in the waiting queue
of a change in the locked object’s state. The method Wait(object) waits for the
Monitor Pulse.
// waitingthread.cs
using System;
using System.Threading;
class WaitingThread
{
static object obj = new object();
public static void thread1()
{
for(int i=1;i<6;i++)
Console.WriteLine("Welcome: {0}",i);
Monitor.Enter(obj);
Monitor.Pulse(obj);
Monitor.Exit(obj);
}
9.4. SYNCHRONIZATION 121
public static void thread2()
{
for(int i=1;i<15;i++)
Console.WriteLine("Hello: {0}",i);
Monitor.Enter(obj);
Monitor.Pulse(obj);
Monitor.Exit(obj);
}
public static void thread3()
{
for(int i=1;i<8;i++)
Console.WriteLine("Good Night: {0}",i);
Monitor.Enter(obj);
Monitor.Wait(obj);
Monitor.Pulse(obj);
Monitor.Exit(obj);
}
public static void Main()
{
ThreadStart job1 = new ThreadStart(thread1);
ThreadStart job2 = new ThreadStart(thread2);
ThreadStart job3 = new ThreadStart(thread3);
Thread t1 = new Thread(job1);
Thread t2 = new Thread(job2);
Thread t3 = new Thread(job3);
t1.Start();
t2.Start();
t3.Start();
}
}
If thread3 runs last after thread1 and thread2 have completed it freeze at
Good Night: 7
9.4 Synchronization
When two or more threads share a common resource access needs to be serialized in a
process called synchronization. Synchronization is done with the lock() operation.
The first program is without the lock operation. In the second program the lock
operation is introduced.
122 CHAPTER 9. PROCESSES AND THREADS
// syncthreads1.cs
using System;
using System.Threading;
class Account
{
private double balance = 5000;
public void Withdraw(double amount)
{
Console.WriteLine("WITHDRAWING {0}",amount);
if(amount > balance) throw new Exception("INSUFFICIENT FUNDS");
Thread.Sleep(10); // do other stuff
balance -= amount;
Console.WriteLine("BALANCE {0}",balance);
}
}
class SymnThread
{
static Account acc = new Account();
public static void Run()
{
acc.Withdraw(3000);
}
public static void Main()
{
new Thread(new ThreadStart(Run)).Start();
acc.Withdraw(3000);
}
}
Now we introduce the lock.
// syncthreads2.cs
using System;
using System.Threading;
class Account
{
private double balance = 5000;
9.5. DEADLOCK 123
public void Withdraw(double amount)
{
Console.WriteLine("WITHDRAWING {0}",amount);
lock(this)
{
if(amount > balance) throw new Exception("INSUFFICIENT FUNDS");
Thread.Sleep(10); // do other stuff
balance -= amount;
}
Console.WriteLine("BALANCE {0}",balance);
}
}
class SymnThread
{
static Account acc = new Account();
public static void Run()
{
acc.Withdraw(3000);
}
public static void Main()
{
new Thread(new ThreadStart(Run)).Start();
acc.Withdraw(3000);
}
}
9.5 Deadlock
The second major problem of multi-threading is that of deadlocks. Simply put, this
is when two threads each holds a monitor that the other one wants. Each blocks,
waiting for the monitor that it’s waiting for to be released - and so the monitors are
never released, and the application hangs (or at least those threads involved in the
deadlock hang). An example is given below.
// Deadlock.cs
using System;
using System.Threading;
public class Deadlock
{
124 CHAPTER 9. PROCESSES AND THREADS
static readonly object firstLock = new object();
static readonly object secondLock = new object();
static void ThreadJob()
{
Console.WriteLine("\t\t\t\tLocking firstLock");
lock(firstLock)
{
Console.WriteLine("\t\t\t\tLocked firstLock");
// Wait until we are fairly sure the first thread
// has grabbed secondlock
Thread.Sleep(1000);
Console.WriteLine("\t\t\t\tLocking secondLock");
lock(secondLock)
{
Console.WriteLine("\t\t\t\tLocked secondLock");
}
Console.WriteLine("\t\t\t\tReleased secondLock");
}
Console.WriteLine("\t\t\t\tReleased firstLock");
} // end method ThreadJob
public static void Main()
{
new Thread(new ThreadStart(ThreadJob)).Start();
// wait until we are fairly sure the other thread
// has grabbed firstlock
Thread.Sleep(500);
Console.WriteLine("Locking secondLock");
lock(secondLock)
{
Console.WriteLine("Locked secondLock");
Console.WriteLine("Locking firstLock");
lock(firstLock)
{
Console.WriteLine("Locked firstLock");
}
Console.WriteLine("Released firstLock");
}
Console.WriteLine("Released secondLock");
} // end Main
} // end class
9.6. INTERLOCKED CLASS 125
9.6 Interlocked Class
An operation is atomic if it is indivisible - in other words, nothing else can happen
in the middle. Thus with an atomic write, we cannot have another thread reading
the value half way through the write, and ending up “seeing” half of the old value
and half of the new value. Sinilarly, with an atomic read, we cannot have another
thread changing the value half way through the read, ending up with a value which
is neither the old nor the new value. For example, for a long (64 bits) on a 32 bit
machine, if one thread is changing the value from 0 to 0x0123456789ABCDEF, there
is no guarantee that another thread will not see the value as 0x0123456700000000
or 0x0000000089ABCDEF.
The Interlocked class provides a set of methods for performing atomic changes: exchanges
(optionally performing a comparison first), increments and decrements. The
Exchange and CompareExchange methods act on a variables of type int, object,
or float; the Increment and Decrement methods act on variables of type int and
long.
// interlocked.cs
using System;
using System.Threading;
public class MyInterlocked
{
static long count = 0;
public static void Main()
{
ThreadStart job = new ThreadStart(ThreadJob);
Thread thread = new Thread(job);
thread.Start();
for(long i=0;i<5;i++)
{
Interlocked.Increment(ref count);
Console.WriteLine("I am in for loop in main");
}
thread.Join();
Console.WriteLine("final count: {0}",count);
} // end Main
static void ThreadJob()
{
long i = 0;
126 CHAPTER 9. PROCESSES AND THREADS
while(i < 5)
{
Interlocked.Increment(ref count);
Console.WriteLine("I am in ThreadJob");
i++;
}
} // end ThreadJob
}
First the for loop in Main will run to the end and then the while loop will be done.
9.7 Thread Pooling
We can use thread pooling to make much more efficient use of multiple threads,
depending on our application. Many applications use multiple threads, but often
those threads spend a great deal of time in the sleeping state waiting for an event
to occur. Other threads might enter a sleeping state and be awakend only periodically
to poll for a change or update status information before going to sleep again.
Using thread pooling provides our application with a pool of worker threads that
are managed by the system, allowing us to concentrate on application tasks rather
than thread management. An example is given below.
// ThreadsSum2.cs
using System;
using System.Threading;
class ThreadsSum
{
public static void Sum1(Object o)
{
int sum1 = 0;
int[] a1 = (int[]) o;
for(int i=0;i
{
sum1 += a1[i];
}
Console.WriteLine("sum1 = " + sum1);
}
public static void Sum2(Object o)
{
int sum2 = 0;
int[] a2 = (int[]) o;
for(int i=0;i
9.8. THREADING IN WINDOWS FORMS 127
{
sum2 += a2[i];
}
Console.WriteLine("sum2 = " + sum2);
}
public static void Main()
{
int[] a1 = { 2, 3, 4 };
int[] a2 = { 4, 5, 7 };
if(ThreadPool.QueueUserWorkItem(new WaitCallback(Sum1),a1))
Console.WriteLine("Sum1 queued");
if(ThreadPool.QueueUserWorkItem(new WaitCallback(Sum2),a2))
Console.WriteLine("Sum2 queued");
Thread.Sleep(10); // Give other threads a turn
}
}
9.8 Threading in Windows Forms
How to handle threading in a UI? There are two rules for Windows Forms:
1) Never invoke any method or property on a control created on another thread other
than Invoke, BeginInvoke, EndInvoke or CreateGraphics, and InvokeRequired.
Each control is effectively bound to a thread which runs its message pump. If we
try to access or change anything in the UI (for example changing the Text property)
from a different thread, we run a risk of our program hanging or misbehaving
in other ways. We may get away with it in some cases. Fortunately, the Invoke,
BeginInvoke and EndInvoke methods have been provided so that we can ask the
UI thread to call a method in a safe manner.
2) Never execute a long-running piece of code in the UI thread. If our code is running
in the UI thread, that means no other code is running in that thread. That means
we won’t receive events, our controls won’t be repainted, etc. We can execute longrunning
code and periodically call Application.DoEvents(). It means we have
to consider re-entrancy issues etc, which are harder to diagnose and fix than ”normal”
threading problems. We have to judge when to call DoEvents, and we can’t
use anything which might block (network access, for instance) without risking an
unresponsive UI. There are message pumping issues in terms of COM objects as well.
If we have a piece of long-running code which we need to execute, we need to create
a new thread (or use a thread pool thread if we prefer) to execute it on, and make
128 CHAPTER 9. PROCESSES AND THREADS
sure it doesn’t directly try to update the UI with its results. The thread creation
part is the same as any other threading problem. It is interesting going the other
way - invoking a method on the UI thread in order to update the UI. There are two
different ways of invoking a method on the UI thread, one synchronous (Invoke)
and one asynchronous (BeginInvoke). They work in much the same way - we specify
a delegate and (optionally) some arguments, and a message goes on the queue
for the UI thread to process. If we use Invoke, the current thread will block until
the delegate has been executed. If we use BeginInvoke, the call will return immediately.
If we need to get the return value of a delegate invoked asynchronously, we
can use EndInvoke with the IAsyncResult returned by BeginInvoke to wait until
the delegate has completed and fetch the return value.
There are two options when working out how to get information between the various
threads involved. The first option is to have state in the class itself, setting it in
one thread, retrieving and processing it in the other (updating the display in the UI
thread, for example). The second option is to pass the information as parameters
in the delegate. Using state somewhere is necessary if we are creating a new thread
rather than using the thread pool - but that doesn’t mean we have to use state to
return information to the UI. However, creating a delegate with lots of parameters
feels clumsy, and is in some ways less efficient than using a simple MethodInvoker
or EventHandler delegate. These two delegates are treated in a special (fast) manner
by Invoke and BeginInvoke. MethodInvoker is just a delegate which takes no
parameters and returns no value (like ThreadStart), and EventHandler takes two
parameters (a sender and an EventArgs parameter and returns no value. However if
we pass an EventHandler delegate to Invoke or BeginInvoke then even if we specify
parameters ourself, they are ignored - when the method is invoked, the sender will
be the control we have invoked it with, and the EventArgs will be EventArgs.Empty.
Here is an example which shows several of the above concepts.
// ThreadingForms.cs
using System;
using System.Threading;
using System.Windows.Forms;
using System.Drawing;
public class Test : Form
{
delegate void StringParameterDelegate(string value);
Label statusIndicator;
Label counter;
Button button;
readonly object stateLock = new object();
9.8. THREADING IN WINDOWS FORMS 129
int target;
int currentCount;
Random rng = new Random();
Test()
{
Size = new Size(180,120);
Text = "Test";
Label lbl = new Label();
lbl.Text = "Status:";
lbl.Size = new Size(50,20);
lbl.Location = new Point(10,10);
Controls.Add(lbl);
lbl = new Label();
lbl.Text = "Count:";
lbl.Size = new Size(50,20);
lbl.Location = new Point(10,34);
Controls.Add(lbl);
statusIndicator = new Label();
statusIndicator.Size = new Size(100,20);
statusIndicator.Location = new Point(70,10);
Controls.Add(statusIndicator);
counter = new Label();
counter.Size = new Size(100,20);
counter.Location = new Point(70,34);
Controls.Add(counter);
button = new Button();
button.Text = "Run";
button.Size = new Size(50,20);
button.Location = new Point(10,58);
Controls.Add(button);
button.Click += new EventHandler(StartThread);
}
void StartThread(object sender,EventArgs e)
{
button.Enabled = false;
lock(stateLock)
{
130 CHAPTER 9. PROCESSES AND THREADS
target = rng.Next(100);
}
Thread t = new Thread(new ThreadStart(ThreadJob));
t.IsBackground = true;
t.Start();
}
void ThreadJob()
{
MethodInvoker updateCounterDelegate = new MethodInvoker(UpdateCount);
int localTarget;
lock(stateLock)
{
localTarget = target;
}
UpdateStatus("Starting");
lock(stateLock)
{
currentCount = 0;
}
Invoke(updateCounterDelegate);
// Pause before starting
Thread.Sleep(500);
UpdateStatus("Counting");
for(int i=0;i
{
lock(stateLock)
{
currentCount = i;
}
// Synchronously show the counter
Invoke(updateCounterDelegate);
Thread.Sleep(100);
}
UpdateStatus("Finished");
Invoke(new MethodInvoker(EnableButton));
}
void UpdateStatus(string value)
{
if(InvokeRequired)
{
// We’re not in the UI thread, so we need to call BeginInvoke
BeginInvoke(new StringParameterDelegate(UpdateStatus),new object[]{value});
9.8. THREADING IN WINDOWS FORMS 131
return;
}
// Must be on the UI thread if we’ve got this far
statusIndicator.Text = value;
}
void UpdateCount()
{
int tmpCount;
lock(stateLock)
{
tmpCount = currentCount;
}
counter.Text = tmpCount.ToString();
}
void EnableButton()
{
button.Enabled = true;
}
static void Main()
{
Application.Run(new Test());
}
}
State is used to tell the worker thread what number to count up to. A delegate taking
a parameter is used to ask the UI to update the status label. The worker thread’s
principal method actually just calls UpdateStatus, which uses InvokeRequired
to detect whether or not it needs to ”change thread”. If it does, it then calls
BeginInvoke to execute the same method again from the UI thread. This is quite
a common way of making a method which interacts with the UI thread-safe. The
choice of BeginInvoke rather than Invoke here was just to demonstrate how to
invoke a method asynchronously. In real code, we would decide based on whether
we needed to block to wait for the access to the UI to complete before continuing
or not. It is quite rare to actually require UI access to complete first, so we should
use BeginInvoke instead of Invoke. Another approach might be to have a property
which did the appropriate invoking when necessary. It is easier to use from the
client code, but slightly harder work in that we would either have to have another
method anyway, or get the MethodInfo for the property setter in order to construct
the delegate to invoke. In this case we actually know that BeginInvoke is required
because we are running in the worker thread anyway. We do not call EndInvoke after
the BeginInvoke. Unlike all other asynchronous methods we do not need to call
EndInvoke unless we need the return value of the delegate’s method. BeginInvoke
132 CHAPTER 9. PROCESSES AND THREADS
is also different to all of the other asynchronous methods as it doesn’t cause the
delegate to be run on a thread pool thread. State is used again to tell the UI thread
how far we have counted so far. We use a MethodInvoker delegate to execute
UpdateCount. We call this using Invoke to make sure that it executes on the UI
thread. This time there’s no attempt to detect whether or not an Invoke is required.
If we call BeginInvoke it will have a different effect than calling the method directly
as it will occur later, rather than in the current execution flow, of course. Again,
we actually know that we need to call Invoke here anyway. A button is provided to
let the user start the thread. It is disabled while the thread is running, and another
MethodInvoker delegate is used to enable the button again afterwards. All state
which is shared between threads (the current count and the target) is accessed in
locks in the way described earlier. We spend as little time as possible in the lock, not
updating the UI or anything else while holding the lock. This probably doesn’t make
too much difference here. It would be disastrous to still have the lock in the worker
thread when synchronously invoking UpdateCount - the UI thread would then try
to acquire the lock as well, and we end up with deadlock. The worker thread is
set to be a background thread (IsBackground=true;) so that when the UI thread
exits, the whole application finishes. In other cases where we have a thread which
should keep running even after the UI thread has quit, we need to be careful not
to call Invoke or BeginInvoke when the UI thread is no longer running - we will
either block permanently (waiting for the message to be taken off the queue, with
nothing actually looking at messages) or receive an exception.
9.9 Asynchronous Programming Model
When a caller invokes a method, the call is synchronous that is the caller has to
wait for the method to return before the remaining code can be executed. .NET has
an inbuilt support for asynchronous method invocation. Here the caller can issue a
request for invocation of a method and concurreently execute the remaining code.
For every delegate declared in an assembly the compiler emits a class (subclass of
System.MulticastDelegate) with Invoke, BeginInvoke and EndInvoke methods.
For example, consider a delegate declared as
delegate int MyWorker(char c,int m);
The compiler will emit the MyWorker class
class MyWorker : System.MulticastDelegate
{
public int Invoke(char c,int m);
public System.IAsyncResult BeginInvoke(char c,int m,System.AsyncCallback cb,
object asyncState);
public int EndInvoke(System.IAsyncResult result);
}
9.10. TIMERS 133
The BeginInvoke and EndInvoke methods can be used for asynchronous invocation
of a method pointed by MyWorker delegate. In the program below the method
DoWork (it displays character c m number of times) is invoked asynchronously with
character ’+’, in the next statement this method is directly invoked with character
’*’. Both ’+’ and ’*’ are displayed (1000 times each) on the Console simultaneously.
As an asynchronous call executes within its own background thread, we have
used Console.Read() to pause the main thread.
// Asynchronous.cs
using System;
delegate int MyWorker(char c,int m);
class AsncTest
{
static MyWorker worker;
static int DoWork(char c,int m)
{
int t = Environment.TickCount; // returns number of milliseconds
// elapsed since the system started
for(int i=1;i<=m;i++) Console.Write(c);
return (Environment.TickCount - t);
}
public static void Main()
{
Console.WriteLine("Start");
worker = new MyWorker(DoWork);
worker.BeginInvoke(’+’,1000,null,null); // asynchronous call
DoWork(’*’,1000); // synchronous call
Console.Read(); // pause until user presses a key
}
}
9.10 Timers
There are various different timers available in .NET, each of which basically calls
a delegate after a certain amount of time has passed. All the timers implement
IDispossible. so we have to make sure to dispose when we are not using them
anymore.
// Timers.cs
134 CHAPTER 9. PROCESSES AND THREADS
using System;
using System.Threading;
public class Timers
{
public static void Main()
{
Console.WriteLine("Started at {0:HH:mm:ss.fff}",DateTime.Now);
// Start in three seconds, then fire every second
using(Timer timer = new Timer(new TimerCallback(Tick),null,3000,1000))
{
// wait for 10 seconds
Thread.Sleep(10000);
// then go slow for another 10 seconds
timer.Change(0,2000);
Thread.Sleep(10000);
}
// the timerm will now have been disposed automatically due
// to the using statement so there won’t be any other threads running
} // end Main()
static void Tick(object state)
{
Console.WriteLine("Ticked at {0:HH:mm:ss.fff}",DateTime.Now);
}
}
9.11 Interrupt and Abort
There are two methods in the Thread class which are used for stopping threads -
Abort and Interrupt. Calling Thread.Abort aborts that thread as soon as possible.
Aborting a thread which is executing unmanaged code has no effect until the CLR
gets control again. Calling Thread.Interrupt is similar, but less drastic. This
causes a ThreadInterruptedException exception to be thrown the next time the
thread enters the WaitSleepJoin state, or immediately if the thread is already in
that state.
// Interruptthread.cs
using System;
using System.Threading;
class SleepingThread
{
public static void Run()
9.11. INTERRUPT AND ABORT 135
{
for(int i=1;i<6;i++)
Console.WriteLine("Welcome {0}",i);
try
{
Thread.Sleep(5000);
}
catch(ThreadInterruptedException e)
{
Console.WriteLine("Sleep Interrupted");
}
Console.WriteLine("Goodbye");
}
public static void Main()
{
Thread t = new Thread(new ThreadStart(Run));
t.Start();
for(int i=1;i<16;i++)
Console.WriteLine("Hello {0}",i);
t.Interrupt();
}
}
The program threadtest1.cs uses the Abort method.
// threadtest1.cs
using System;
using System.Threading; // for Thread class
class ThreadTest1
{
public static void SayHello()
{
for(int i=1;;i++)
{
Console.WriteLine("Hello {0}",i);
}
}
public static void Main()
{
Thread t = new Thread(new ThreadStart(SayHello));
136 CHAPTER 9. PROCESSES AND THREADS
t.Start();
for(int i=1;i<1001;i++)
{
Console.WriteLine("Bye {0}",i);
}
t.Abort();
}
}
No comments:
Post a Comment