0% found this document useful (0 votes)
48 views

C# Threading: Hans-Wolfgang Loidl

The document discusses processes and threads in C#. It states that traditionally a process contained a single thread of execution, but modern operating systems allow processes to contain multiple threads to enable concurrency. It provides examples of how to create threads in C# using the Thread class and start method, and how to use locking to control access to shared resources across threads through mutual exclusion.
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
48 views

C# Threading: Hans-Wolfgang Loidl

The document discusses processes and threads in C#. It states that traditionally a process contained a single thread of execution, but modern operating systems allow processes to contain multiple threads to enable concurrency. It provides examples of how to create threads in C# using the Thread class and start method, and how to use locking to control access to shared resources across threads through mutual exclusion.
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 35

C# Threading

Hans-Wolfgang Loidl
<[email protected]>
School of Mathematical and Computer Sciences,
Heriot-Watt University, Edinburgh

Semester 1 — 2018/19

0
Based on: "An Introduction to programming with C# Threads"
By Andrew Birrell, Microsoft, 2005 hwu-logo.png
Examples from "Programming C# 5.0", Jesse Liberty, O’Reilly. Chapter 20.
H-W. Loidl (Heriot-Watt Univ) F20SC/F21SC — 2018/19 C# Threading 1 / 33
Processes and Threads

Traditionally, a process in an operating system consists of


an execution environment and a single thread of
execution (single activity).
However, concurrency can be required in many programs
(e.g in GUIs) for various reasons.
The solution was to improve the notion of a process to
contain an execution environment and one or more
threads of execution.

hwu-logo.png

H-W. Loidl (Heriot-Watt Univ) F20SC/F21SC — 2018/19 C# Threading 2 / 33


Processes and Threads (cont’d)

An execution environment is a collection of kernel


resources locally managed, which threads have access to.
It consists of:
I An address space.
I Threads synchronization and communication resources
I Higher-level resources such as file access.

hwu-logo.png

H-W. Loidl (Heriot-Watt Univ) F20SC/F21SC — 2018/19 C# Threading 3 / 33


Processes and Threads (cont’d)

Threads represent activities which can be created and


destroyed dynamically as required and several of them
can be running on a single execution environment.
The aim of using multiple threads in a single environment
is:
I To maximise the concurrency of execution between
operations, enabling the overlap of computation with
input and output.
I E.g. one thread can execute a client request while another
thread serving another request (optimising server
performance).

hwu-logo.png

H-W. Loidl (Heriot-Watt Univ) F20SC/F21SC — 2018/19 C# Threading 4 / 33


Cincurrency and Parallelism

In some applications concurrency is a natural way of


structuring your program:
I In GUIs separate threads handle separate events
Concurrency is also useful operating slow devices
including e.g. disks and printers.
I IO operations are implemented as a separate thread while
the program is progressing through other threads.
Concurrency is required to exploit multi-processor
machines.
I Allowing processes to use the available processors rather
than one.

hwu-logo.png

H-W. Loidl (Heriot-Watt Univ) F20SC/F21SC — 2018/19 C# Threading 5 / 33


Sources of Concurrency

Concurrency aides user interaction:


I Program could be processing a user request in the
background and at the same time responding to user
interactions by updating GUI.
Concurrency aides performance:
I A web server is multi-threaded to be able to handle
multiple user requests concurrently.

hwu-logo.png

H-W. Loidl (Heriot-Watt Univ) F20SC/F21SC — 2018/19 C# Threading 6 / 33


Thread Primitives

Thread Creation.
Mutual Exclusion.
Event waiting.
Waking up a thread.
The above primitives are supported by C#’s
System.Threading namespace and C# lock statement.

hwu-logo.png

H-W. Loidl (Heriot-Watt Univ) F20SC/F21SC — 2018/19 C# Threading 7 / 33


Thread Creation

A thread is constructed in C# by:


I Creating a Thread object.
I Passing to it a ThreadStart delegate.
I Calling the start method of the created thread.
Creating and starting a thread is called forking.

hwu-logo.png

H-W. Loidl (Heriot-Watt Univ) F20SC/F21SC — 2018/19 C# Threading 8 / 33


Thread Creation Example
1 Thread t = new Thread ( new ThreadStart ( func . A ) ) ;
2
Main thread executing
3 t . start () ;
4
5 func . B () ;
6
7 t . join () ;

The code above executes functions func.A() and


func.B() concurrently.
Initially, only the main thread is executing.
In Line 3, Thread t is created and started.
While Thread t is executing func.A(), the main thread is
executing func.B()
Execution completes when both method calls have
completed. hwu-logo.png

H-W. Loidl (Heriot-Watt Univ) F20SC/F21SC — 2018/19 C# Threading 9 / 33


Thread Creation Example
1 Thread t = new Thread ( new ThreadStart ( func . A ) ) ;
2
Main thread executing
3 t . start () ;
4
Thread t started, executing func.A()
5 func . B () ;
6
main exec func.B(), t exec func.A()
7 t . join () ;

The code above executes functions func.A() and


func.B() concurrently.
Initially, only the main thread is executing.
In Line 3, Thread t is created and started.
While Thread t is executing func.A(), the main thread is
executing func.B()
Execution completes when both method calls have
completed. hwu-logo.png

H-W. Loidl (Heriot-Watt Univ) F20SC/F21SC — 2018/19 C# Threading 9 / 33


Thread Creation Example
1 Thread t = new Thread ( new ThreadStart ( func . A ) ) ;
2
Main thread executing
3 t . start () ;
4
Thread t started, executing func.A()
5 func . B () ;
6
main exec func.B(), t exec func.A()
7 t . join () ;
Waiting for both threads to complete
The code above executes functions func.A() and
func.B() concurrently.
Initially, only the main thread is executing.
In Line 3, Thread t is created and started.
While Thread t is executing func.A(), the main thread is
executing func.B()
Execution completes when both method calls have
completed. hwu-logo.png

H-W. Loidl (Heriot-Watt Univ) F20SC/F21SC — 2018/19 C# Threading 9 / 33


Mutual Exclusion

Mutual exclusion is required to control threads access to


a shared resource.
We need to be able to specify a region of code that only
one thread can execute at any time.
Sometimes called critical section.

hwu-logo.png

H-W. Loidl (Heriot-Watt Univ) F20SC/F21SC — 2018/19 C# Threading 10 / 33


Mutual Exclusion in C#
1 lock ( expression )
2 statement

Mutual exclusion is supported in C# by class Monitor


and the lock statement.
The lock argument can be any C# object.
By default, C# objects are unlocked.
The lock statement
I locks the object passed as its argument,
I executes the statements,
I then unlocks the object.
If another thread attempts to access the locked object,
the second thread is blocked until the lock releases the
object. hwu-logo.png

H-W. Loidl (Heriot-Watt Univ) F20SC/F21SC — 2018/19 C# Threading 11 / 33


Example: Swap

1 public void Swap () {


2 lock ( this ) {
3 Console . WriteLine ( " Swap ␣ enter : ␣ x ={0} , ␣ y ={1} " ,
4 this .x , this . y ) ;
5 int z = this . x ;
6 this . x = this . y ;
7 this . y = z ;
8 Console . WriteLine ( " Swap ␣ leave : ␣ x ={0} , ␣ y ={1} " ,
9 this .x , this . y ) ;
10 }
11 }

hwu-logo.png
0
Examples from “Programming C# 3.0”, Jesse Liberty, O’Reilly. Chapter 20.
H-W. Loidl (Heriot-Watt Univ) F20SC/F21SC — 2018/19 C# Threading 12 / 33
Example: Swap (cont’d)

1 public void DoTest () {


2 Thread t1 = new Thread ( new ThreadStart ( Swap ) ) ;
3 Thread t2 = new Thread ( new ThreadStart ( Swap ) ) ;
4 t1 . Start () ;
5 t2 . Start () ;
6 t1 . Join () ;
7 t2 . Join () ;
8 }

hwu-logo.png

H-W. Loidl (Heriot-Watt Univ) F20SC/F21SC — 2018/19 C# Threading 13 / 33


Waiting for a Condition

Locking an object is a simple scheduling policy.


The shared memory accessed inside the lock statement is
the scheduled resource.
I More complicated scheduling is sometimes required.
I Blocking a thread until a condition is true.
I Supported in C# using the Wait, Pulse and PulseAll
functions of class Monitor.

hwu-logo.png

H-W. Loidl (Heriot-Watt Univ) F20SC/F21SC — 2018/19 C# Threading 14 / 33


Waiting for a Condition (cont’d)

A thread must hold the lock to be able to call the Wait


function.
The Wait call unlocks the object and blocks the thread.
The Pulse function awakens at least one thread blocked
on the locked object.
The PulseAll awakens all threads currently waiting on the
locked object.
When a thread is awoken after calling Wait and blocking,
it re-locks the object and return.

hwu-logo.png

H-W. Loidl (Heriot-Watt Univ) F20SC/F21SC — 2018/19 C# Threading 15 / 33


Example: Increment/Decrement
1 public void Decrementer () {
2 try {
3 // synchronise this area
4 Monitor . Enter ( this ) ;
5 if ( counter < 1) {
6 Console . WriteLine ( " In ␣ Decrementer . ␣ Counter : ␣ {1} " ,
7 Thread . CurrentThread . Name , counter ) ;
8 Monitor . Wait ( this ) ;
9 }
10

11 while ( counter > 0) {


12 long temp = counter ;
13 temp - -;
14 Thread . Sleep (1) ;
15 counter = temp ;
16 Console . WriteLine ( " In ␣ Decrementer . ␣ Counter :{1} " ,
17 Thread . CurrentThread . Name , counter ) ;
18 } } finally { hwu-logo.png
19 Monitor . Exit ( this ) ;
20 }H-W.}Loidl (Heriot-Watt Univ) F20SC/F21SC — 2018/19 C# Threading 16 / 33
Example: Increment/Decrement (cont’d)
1 public void Incrementer () {
2 try {
3 // synchronise this area
4 Monitor . Enter ( this ) ;
5
6 while ( counter < 10) {
7 long temp = counter ;
8 temp ++;
9 Thread . Sleep (1) ;
10 counter = temp ;
11 Console . WriteLine ( " In ␣ Incrementer .{1}. " ,
12 Thread . CurrentThread . Name , counter ) ;
13 }
14 Monitor . Pulse ( this ) ;
15 } finally {
16 Console . WriteLine ( " Exiting ␣ ... " ,
17 Thread . CurrentThread . Name ) ;
18 Monitor . Exit ( this ) ; hwu-logo.png
19 } }
H-W. Loidl (Heriot-Watt Univ) F20SC/F21SC — 2018/19 C# Threading 17 / 33
Example: Increment/Decrement (cont’d)
1 public void DoTest () {
2 Thread [] myThreads = {
3 new Thread ( new ThreadStart ( Decrementer ) ) ,
4 new Thread ( new ThreadStart ( Incrementer ) ) };
5
6 int n = 1;
7 foreach ( Thread myThread in myThreads ) {
8 myThread . IsBackground = true ;
9 myThread . Name = " Thread " + n . ToString () ;
10 Console . WriteLine ( " Starting ␣ thread ␣ {0} " ,
myThread . Name ) ;
11 myThread . Start () ;
12 n ++;
13 Thread . Sleep (500) ;
14 }
15 foreach ( Thread myThread in myThreads ) {
16 myThread . Join () ;
17 } hwu-logo.png
18 Console . WriteLine ( " All ␣ my ␣ threads ␣ are ␣ done " ) ;
19 } (Heriot-Watt Univ)
H-W. Loidl F20SC/F21SC — 2018/19 C# Threading 18 / 33
Example explained

2 threads are created: one for incrementing another for


decrementing a global counter
A monitor is used to ensure that reading and writing of
the counter is done atomically
Monitor.Enter/Exit are used for entering/leaving an
atomic block (critical section).
The decrementer first checks whether the value can be
decremented.
Monitor.Pulse is used to inform the waiting thread of a
status change.

hwu-logo.png

H-W. Loidl (Heriot-Watt Univ) F20SC/F21SC — 2018/19 C# Threading 19 / 33


Thread Interruption

Interrupting a thread is sometimes required to get the


thread out from a wait.
This can be achieved in C# by using the interrupt
function of the Thread class.
A thread t in a wait state can be interrupted by another
thread by calling t.interrupt().
I t will then resume execution by relocking the object
(maybe after waiting for the lock to become unlocked).
Interrupts complicate programs and should be avoided if
possible.

hwu-logo.png

H-W. Loidl (Heriot-Watt Univ) F20SC/F21SC — 2018/19 C# Threading 20 / 33


Race Conditions

Example:
Thread A opens a file
Thread B writes to the file
I =⇒ The program is successful, if A is fast enough to open
the file, before B starts writing.

hwu-logo.png

H-W. Loidl (Heriot-Watt Univ) F20SC/F21SC — 2018/19 C# Threading 21 / 33


Deadlocks

Thread A locks object M1


Thread B locks object M2
Thread A blocks trying to lock M2
Thread B blocks trying to lock M1
=⇒ None of the 2 threads can make progress

hwu-logo.png

H-W. Loidl (Heriot-Watt Univ) F20SC/F21SC — 2018/19 C# Threading 22 / 33


Avoiding Deadlocks

Maintain a partial order for acquiring locks in the


program.
For any pair of objects M1, M2, each thread that needs
to have both objects locked simultaneously should lock
the objects in the same order.
E.g. M1 is always locked before M2.
=⇒ This avoids deadlocks caused by locks.

hwu-logo.png

H-W. Loidl (Heriot-Watt Univ) F20SC/F21SC — 2018/19 C# Threading 23 / 33


Deadlocks caused by waits

Example:
Thread A acquires resource 1
Thread B acquires resource 2
Thread A wants 2, so it calls Wait to wait for 2
Thread B wants 1, so it calls Wait to wait for 1
=⇒ Again, partial order can be used to avoid the
deadlock.

hwu-logo.png

H-W. Loidl (Heriot-Watt Univ) F20SC/F21SC — 2018/19 C# Threading 24 / 33


Other Potenital Problems

Starvation: When locking objects or using


Monitor.Wait() on an object, there is a risk that the
object will never make progress.
Program complexity.

hwu-logo.png

H-W. Loidl (Heriot-Watt Univ) F20SC/F21SC — 2018/19 C# Threading 25 / 33


Background Worker

BackgroundWorker is a helper class in the


System.ComponentModel namespace for managing a
worker thread.
To use it you need to
I Instantiate BackgroundWorker and handle the DoWork
event.
I Call RunWorkerAsync, optionally with an object argument.
Any argument passed to RunWorkerAsync will be
forwarded to DoWork’s event handler, via the event
argument’s Argument property.
For more info on monitoring progress, cancellation of
work etc, follow the link below.
hwu-logo.png
0
See this section in “Threading in C#”, by Joe Albahari
H-W. Loidl (Heriot-Watt Univ) F20SC/F21SC — 2018/19 C# Threading 26 / 33
Background Worker Example
1 class Program {
2 static BackgroundWorker _bw = new BackgroundWorker () ;
3
4 static void Main () {
5 _bw . DoWork += bw_DoWork ; // register the method
to be called
6 _bw . RunWorkerAsync ( " Message ␣ to ␣ worker " ) ; // run
the method asynchronously
7 Console . ReadLine () ;
8 }
9
10 static void bw_DoWork ( object sender , DoWorkEventArgs
e) {
11 // This is called on the worker thread
12 Console . WriteLine ( e . Argument ) ; // writes "
Message to worker "
13 // Perform time - consuming task ...
14 } hwu-logo.png
15 }
H-W. Loidl (Heriot-Watt Univ) F20SC/F21SC — 2018/19 C# Threading 27 / 33
The async & await constructs
The async & await constructs provide language support to
implement asynchronous methods without the need to
generate threads explicitly:
A method can have the modifier async to indicate that it
is an asynchronous methods
The return type of the method is then of the form
Task<TResult>, i.e. the method returns a handle to the
computation that is producing a result
The await keyword is used to wait for the result that is
being generated by an asynchronous method
While the asynchronous method waits for the result,
control returns to the caller of the async method.
hwu-logo.png
0
See this MSDN article on “Threading and Asynchronous Programming”
H-W. Loidl (Heriot-Watt Univ) F20SC/F21SC — 2018/19 C# Threading 28 / 33
Example of async/await

Asynchronous file reading (main interface):


1 public async Task ProcessRead ( string filePath ) {
2 try {
3 string text = await ReadTextAsync ( filePath ) ;
4 Console . WriteLine ( text ) ;
5 } catch ( Exception ex ) {
6 Console . WriteLine ( ex . Message ) ;
7 }
8 }

hwu-logo.png

H-W. Loidl (Heriot-Watt Univ) F20SC/F21SC — 2018/19 C# Threading 29 / 33


Example of async/await
Asynchronous file reading (low-level implementation):
1 private async Task < string > ReadTextAsync ( string
filePath ) {
2 using ( FileStream sourceStream =
3 new FileStream ( filePath ,
4 FileMode . Open , FileAccess . Read ,
FileShare . Read ,
5 bufferSize : 4096 , useAsync : true ) ) {
6 StringBuilder sb = new StringBuilder () ;
7 byte [] buffer = new byte [0 x1000 ];
8 int numRead ;
9 while (( numRead = await sourceStream . ReadAsync (
buffer , 0 , buffer . Length ) ) != 0) {
10 string text = Encoding . Unicode . GetString (
buffer , 0 , numRead ) ;
11 sb . Append ( text ) ;
12 }
13 return sb . ToString () ; hwu-logo.png
14 }
15 H-W.
} Loidl (Heriot-Watt Univ) F20SC/F21SC — 2018/19 C# Threading 30 / 33
Example of async/await
A tester function, calling an asynchronous method several
times:
1 public async Task DoIt ( params string [] strs ) {
2 Task t ;
3 List < Task > tasks = new List < Task >() ;
4 foreach ( string str in strs ) {
5 t = ProcessRead ( str ) ;
6 tasks . Add ( t ) ;
7 }
8 await Task . WhenAll ( tasks ) ;
9 }

0
See Asynchronous Programming with Async and Await (C# and Visualhwu-logo.png
Basic)
H-W. Loidl (Heriot-Watt Univ) F20SC/F21SC — 2018/19 C# Threading 31 / 33
Resources
Sample sources and background reading:
threads2.cs: incrementer/decrementer
threads4.cs: incrementer/decrementer with marks
mulT.cs: expanded multi-threading example
BgWorker.cs: background worker example
asyncFiles.cs: async example
See this screencast on LinkedIn Learning on “Async
Programming in C#”
See this section in “Threading in C#”, by Joe Albahari
See this MSDN article on “Threading and Asynchronous
Programming”
See Asynchronous Programming with Async and Await
(C# and Visual Basic) hwu-logo.png

H-W. Loidl (Heriot-Watt Univ) F20SC/F21SC — 2018/19 C# Threading 32 / 33


Summary
Technologies for non-blocking behaviour of your code:
Threads are the most powerful mechanism, allowing for
independent strands of computation
I Independent threads also allow the usage of parallelism to
make your program run faster (e.g. one thread per core)
I Managing threads can be difficult and common pitfalls are
deadlocks, race conditions, and starvation
A BackgroundWorker task achieves asynchronous
behaviour without explicitly generating threads.
I The task will run along-side the main application.
I When the task blocks on some operation, the caller can
take over and continue with other parts of the program.
The async/await constructs allow you to compose your
own asynchronous methods
I Simpler than threads or BackgroundWorker, but still
hwu-logo.png
single-threaded, and not suitable for parallel execution.
H-W. Loidl (Heriot-Watt Univ) F20SC/F21SC — 2018/19 C# Threading 33 / 33

You might also like