C# language: Concurrency(multithreading)
Typically, applications or programs on a computer or phone perform more than one operation, this is called concurrency, we will first discuss the basics of concurrency, ie threads and tasks, and in the next lesson we will go to asynchrony.
The most common examples of concurrency:
- In mobile applications, WPF, websites, tasks must be run concurrently with the code responsible for the user interface to maintain responsive.
- The operating system performs some tasks concurrently, eg if you have a computer turned on you usually have more than one program enabled, if the operating system didn’t perform these tasks concurrently, then we could have only one program switched on at a time.
- In portals or online stores, when more than one user uses them in an instant, concurrency allows processing more than one client’s request at once.
Threads
A thread is an execution path that can be executed independently of other paths eg the main thread in some program is eg calculation of an expression so it is single-threaded, and if it were multithreaded, many threads could be running in one moment eg would calculate a value in the same for a moment it could do something else in the background, some a method whose result would display eg after calculating the value.
using System;
using System.Threading;
namespace wyrlam
{
class Program
{
static void Main()
{
Thread x = new Thread(WriteY); // create a new thread
x.Start(); // execute of the method WriteY()
// at the same time, we perform operations in the main thread
Thread y = new Thread(WriteX); // create a new thread
y.Start();// execute of the method WriteX()
Thread z = new Thread(WriteZ); // create a new thread
z.Start();// execute of the method WriteZ()
for (int i = 0; i < 1000; i++) Console.Write("S");
Console.ReadKey();
}
static void WriteY()
{
for (int i = 0; i < 2000; i++) Console.Write("i");
}
static void WriteX()
{
for (int i = 0; i < 3000; i++) Console.Write("e");
}
static void WriteZ()
{
for (int i = 0; i < 4000; i++) Console.Write("m");
}
}
}
In the main thread in the “Main” method we create three other threads, methods that display characters in the console and you can see that at the same time characters from the “Main” method are displaying, i.e. our program is multithreaded.
You can also wait for the thread to finish working, then you need to add the Join() method
Thread x = new Thread(WriteY); // create a new thread
x.Start(); // execute of the method WriteY()
x.Join();
Thread y = new Thread(WriteX); // create a new thread
y.Start();// execute of the method WriteX()
y.Join();
Thread z = new Thread(WriteZ); // create a new thread
z.Start();// execute of the method WriteZ()
z.Join();
You can see the differences that the threads are executing now in turn.
We can also put the thread to sleep for a while, let’s put the thread y to sleep:
Thread y = new Thread(WriteX); // create a new thread
Thread.Sleep(3000); //suspend the thread for 3 seconds
y.Start();// execute of the method WriteX()
y.Join();
In this example, the WriteY() method will be executed immediately, then we have to wait 3 seconds to execute the next threads, but the time must be given in milliseconds.
While there is a problem with threads, look at the example below:
static void Main()
{
Thread z = new Thread(WriteZ);
z.Start();
WriteZ();
Console.ReadKey();
}
static void WriteZ()
{
if (!_done) { Console.WriteLine("Done"); _done = true; }
}
At the same time, two threads are executed that start the same method, this will display the word “Done” twice in the console.
How can you check the security of threads? The keyword “lock” is used for this. So we will secure our threads in the example below.
static bool _done;
static readonly object _locker = new object();
static void Main()
{
Thread z = new Thread(WriteZ);
z.Start();
WriteZ();
Console.ReadKey();
}
static void WriteZ()
{
lock (_locker)
{
if (!_done) { Console.WriteLine("Done"); _done = true; }
}
}
The solution used in the above example guarantees that one block can be put on one thread at a time, which means that the word “Done” will be displayed once.
Sometimes there is a need to pass data to the thread. In this case, the lambda expression is the easiest to use:
static void Main()
{
Thread z = new Thread(() => WriteZ("executing a thread z"));
z.Start();
Console.ReadKey();
}
static void WriteZ(string text)
{
Console.WriteLine(text);
}
Tasks
The thread is a low-level tool designed to provide concurrency and therefore has some limitations.
- It is easy to pass data to it, but it is not easy to get a return value from it.
- You can not tell a thread to execute another thread when it ends. Instead, it is necessary to use the Join() method, which means blocking the thread at that time.
Direct use of threads also worsens performance.
The Task class is the solution to all these problems.
Starting the task
Tasks are created in the following way:
Task.Run(() => Console.WriteLine("Hi"));
This record is the equivalent of threads in this form:
new Thread (() => Console.WriteLine ("Hi")).Start();
Wait()
call Wait() method is the equivalent of the join() method in threads:
static void Main()
{
Task task = Task.Run(() =>
{
Thread.Sleep(2000);
Console.WriteLine("Hi");
});
Console.WriteLine(task.IsCompleted); // False
task.Wait(); // blocking until the task will be completed
Console.WriteLine(task.IsCompleted); // True
Console.ReadKey();
}
Return value
In the threads is difficult to do, trivial in tasks:
static void Main()
{
Task<int> task = Task.Run(() =>
{
return 3;
});
int number = task.Result;
Console.WriteLine(number);
Console.ReadKey();
}
Continuations
The continuations could be translated into the sentence “after finishing doing something else”. There are two ways to define the continuation for the task, the first is important because it is used in asynchronous functions, what you will learn in the next lesson, below is an example:
static void Main()
{
Task<int> primeNumberTask = Task.Run(() =>
{
Thread.Sleep(3000);
return 34;
});
var awaiter = primeNumberTask.GetAwaiter();
awaiter.OnCompleted(() =>
{
int result = awaiter.GetResult();
Console.WriteLine(result); // record of the result
});
Console.ReadKey();
}
The second way to add a continuation is to call the ContinueWith() method:
static void Main()
{
Task<int> primeNumberTask = Task.Run(() =>
{
Thread.Sleep(3000);
return 34;
});
primeNumberTask.ContinueWith(antecedent =>
{
int result = antecedent.Result;
Console.WriteLine(result); // record 34
});
Console.ReadKey();
}
Task.Delay()
The Task.Delay() method is the asynchronous equivalent of Thread.Sleep():
static void Main()
{
Task.Delay(5000).GetAwaiter().OnCompleted(() =>
{
Console.WriteLine("Hi");
});
Console.ReadKey();
}
This content also you can find on my blog http://devman.pl/csharplan/c-language-concurrencymultithreading/
If you recognise it as useful, share it with others so that others can also use it.
Leave upvote and follow and wait for next articles :) .
We have reached the end, the knowledge in this lesson will be need in the next one, so understand the material in it well.