Get up to 80 % extra points for free! More info:

Lesson 2 - Threads in C# .NET - Sleep, Join, and lock

In the previous lesson, Introduction to multi-threaded applications in C#.NET, we created our first multi-threaded application. In today's C# .NET tutorial, we're going to learn how to block and lock threads.

Sleep and Join

We can put the current thread to sleep for a given number of milliseconds using the static Sleep() method on the Thread class. The thread is blocked until the time runs out, then it wakes up again and continues its activity.

Let's create a new project with the Writer class, which will look similar to the Switcher from the previous lesson:

class Writer
{

    public void Write0()
    {
        for (int i = 0; i < 100; i++)
        {
            Console.Write("0");
            Thread.Sleep(5);
        }
    }

    public void Write1()
    {
        for (int i = 0; i < 150; i++)
        {
            Console.Write("1");
            Thread.Sleep(5);
        }
    }

}

The Write0() method writes 100 zeros to the console and puts its thread to sleep for 5ms with each output. The Write1() method writes 150 ones and so it runs longer (for about 1/4 second) than the Write0() method.

Now, in the Main() method, we'll create a thread for each method and run them. Finally, we'll write "Done":

Writer writer = new Writer();
Thread thread1 = new Thread(writer.Write0);
Thread thread2 = new Thread(writer.Write1);
thread1.Start();
thread2.Start();
Console.WriteLine("Done");

The application output is as follows:

Joining threads in C# .NET - Parallel Programming and Multi-Threaded Apps in C# .NET

"Done" was written first because the main thread didn't wait for the writer threads. We can wait for a thread to finish using the Join() method that blocks the current thread until the method finishes. Let's change our code into this one:

Writer writer = new Writer();
Thread thread1 = new Thread(writer.Write0);
Thread thread2 = new Thread(writer.Write1);
thread1.Start();
thread2.Start();
thread1.Join();
thread2.Join();
Console.WriteLine("Done");

The main thread now waits until both threads finish their work. The result is as follows:

Joining threads in C# .NET - Parallel Programming and Multi-Threaded Apps in C# .NET

If we want to put a thread to sleep for a long time, we can pass a TimeSpan instance as a parameter instead of converting hours to seconds. The TimeSpan class has static methods like FromHours() and so on:

Thread.Sleep(TimeSpan.FromHours(2));

If we want the system to switch a thread, we can let it sleep even for 0 ms. Calling Thread.Sleep() will always block the thread. We can achieve a similar effect using the Thread.Yield() method.

We can ask for the thread state using the ThreadState property. It's a flag having one or more of these values: Running, StopRequested, SuspendRequested, Background, Unstarted, Stopped, WaitSleepJoin, Suspended, AbortRequested, Aborted. We use this property mainly for debugging, it's not appropriate for synchronization.

Sharing data between threads

Of course, we often need to share some data between threads, at least for communication. You shouldn't be surprised that if we run the same method in multiple threads, it'll have its own local variables in each thread. For a simple test, let's use the class from the previous example:

Writer writer = new Writer();
Thread thread1 = new Thread(writer.Write0);
thread.Start();
writer.Write0();
Console.ReadKey();

The result:

Sharing thread data in C# .NET - Parallel Programming and Multi-Threaded Apps in C# .NET

Since the console is 80 characters wide by default and less than 3 lines were printed, we can see that both loops ran 100x and that each thread used its i variable.

ThreadSafety

The thread method can access instance or static variables. That's the way threads can communicate with each other. As we already know, the catch will be in the synchronization.

Imagine the following class:

class AtmUnsafe
{
    private decimal cash = 100;

    private void Withdraw100()
    {
        if (cash >= 100)
        {
            Console.WriteLine("I withdraw 100");
            cash -= 100;
            Console.WriteLine("You still have {0} on your account.", cash);
        }
    }

    public void WithdrawThreads()
    {
        Thread thread1 = new Thread(Withdraw100);
        thread1.Start();
        Withdraw100();
        if (cash < 0)
            Console.WriteLine("Cash is negative, we have been robbed.");
    }

}

The class represents an ATM machine that contains some cash. When you create the ATM, the cash is 100 USD. It also has a simple Withdraw100() method that withdraws 100 dollars if there's the required balance in the bank account. We're interested in the WithdrawThreads() method. It tries to withdraw $100 using 2 threads (current and newly created). If the cash balance happens to be negative, it prints a message about that.

We'll add code to the main method, which will make 200 withdrawals from 100 ATMs:

for (int i = 0; i < 100; i++)
{
    AtmUnsafe atm = new AtmUnsafe();
    atm.WithdrawThreads();
}

And we'll run the app:

Thread synchronization in C# .NET - Parallel Programming and Multi-Threaded Apps in C# .NET

We can see from the output that something is wrong. Where's the problem?

We check whether there's sufficient cash in the account with the condition in the Withdraw100() method. Imagine that there is 100 USD in the account. The condition applies and the system can put the thread to sleep, it can happen right after evaluating the condition. So this thread is waiting now. The second thread also checks the condition which still applies and withdraws $100. Then the first thread wakes up, which is already after the condition, and also withdraws $100. As a result, we have a negative balance in our account! We can see that working with threads brings new pitfalls that we haven't encountered before. We'll solve the situation by locks.

Locking

We certainly agree that the code part checking the balance and changing it must always run uninterrupted, otherwise, we'll get into the above-mentioned situation. We'll fix the problem by locking the part where we're working with the shared variable. We'll modify the code:

class AtmSafe
{
    private decimal cash = 100;
    private object myLock = new object();

    public void WithdrawThreads()
    {
        Thread thread1 = new Thread(Withdraw100);
        thread1.Start();
        Withdraw100();
        if (cash < 0)
            Console.WriteLine("Cash is negative, we have been robbed.");
    }

    private void Withdraw100()
    {
        lock (myLock)
        {
            if (cash >= 100)
            {
                Console.WriteLine("I withdraw 100");
                cash -= 100;
                Console.WriteLine("you still have {0} on your account.", cash);
            }
        }
    }
}

We lock using the lock construct that takes a lock as a parameter. The lock can be any object, we create a simple attribute for this purpose. When the system wants to put the thread to sleep, it has to wait until it comes out of the critical section (the one inside the lock).

The application now works properly, and we can declare it ThreadSafe.

Locking threads in C# .NET - Parallel Programming and Multi-Threaded Apps in C# .NET

In the next lesson, Monitors, thread priority, exceptions and more in C# .NET, we'll focus on more thread pitfall, say more about locks and start passing data to threads.


 

Previous article
Introduction to multi-threaded applications in C#.NET
All articles in this section
Parallel Programming and Multi-Threaded Apps in C# .NET
Skip article
(not recommended)
Monitors, thread priority, exceptions and more in C# .NET
Article has been written for you by David Capka Hartinger
Avatar
User rating:
1 votes
The author is a programmer, who likes web technologies and being the lead/chief article writer at ICT.social. He shares his knowledge with the community and is always looking to improve. He believes that anyone can do what they set their mind to.
Unicorn university David learned IT at the Unicorn University - a prestigious college providing education on IT and economics.
Activities