Using threads in C#, is semantically similar to using threads in Java, C or C++, what changes is the form (syntax) with which threads are created/used in different languages.
Answering the question on how to use threads, exhaustively and in general, requires more space and time than provided here, since there are entire courses and disciplines dedicated to it.
However, these are some well-known books, used in undergraduate courses, that can give a good light on the subject (there are others, but these are the most common):
- Distributed Systems - Principles and Paradigms tanenbaum
- Fundamentals of Operating Systems silberschatz
- Modern Operating Systems tanenbaum
Now, taking advantage of the space, in a simplified way, you can imagine a thread as a piece of code that runs parallel to your code, and there can be several threads running at a given time.
From the moment you create a thread, the Operating System knows that, in addition to everything you are already doing, there is more code that needs to be executed. If there are more threads than processors/cores (which is the most seen case on personal computers and mobile devices), then the Operating System starts "scheduling a moment" for each thread to run on a given core/processor (process known as scheduling, or scheduling).
For example, assuming you have a 4-core computer, and the Operating System has 8 threads currently running (TA, TB, TC, TD, TE, TF, TG and TH), then a possible thread scheduling can be like the one in the figure below:
Here, you can see that TA and TB threads share Core 0, TC and TD share Core 1 and so on, so each thread uses 10 milliseconds of each core, then "give it up" to another thread that’s waiting. Although there are 8 threads, only 4 run simultaneously on the processor, while the other 4 are waiting (without consuming processing resources).
Of course, this is a simple example. In real life, scheduling is not as symmetrical, and depends on a number of factors, such as the thread’s priority (the more privilege, the more time it has the processor’s right to use), or even whether the thread is waiting for an external event to occur, which will keep her in a state of waiting much longer. This, by the way, is the reason why threads are so important in current systems.
For example, imagine that in an excerpt of your code you ask to write a text to a file (C code#):
public void GravaArquivo(...) {
//Bloco A
File.WriteAllText(...);
//Bloco B
}
The code in the Bloco B
will not be executed until the method WriteAllText
Finish running. This is known as synchronism: an event only occurs after the previous one has finished altogether. That is, from the point of view of the method GravaArquivo
the execution of that "lock" code on the line File.WriteAllText(...);
until the file has been written to disk.
As brief as it may seem to us humans, this time is too long for the processor, so even the Operating System leaves the thread running the method GravaArquivo
in standby state, until the disk reports the end of the operation. This is the external event, previously mentioned.
It is at this point that the creation of extra threads is necessary.
Imagine the scenario of the figure below, where you click on the "Save" button of a program:
The code of the "Save" button will execute on the thread responsible for the entire interface with the program user, that is, while the execution of the code of the Save button does not finish, it is not possible to click another button of that program, use keyboard commands in that program, or even, update the look of the program window (all part of the user interface, as well as the Save button)!
When the user interface stops responding for a certain time, the operating systems begin to take action, as they have no way of knowing if the code is actually locked (bug), or if it is just taking time. Hence the "Not responding" warnings from Windows, or that Android ANR window.
To avoid these scenarios, good programming practices require time-consuming tasks, such as reading/writing a file, sending/receiving data over a network, etc., to be performed in threads other than the thread used to process the user interface. That’s when you’ll need to create extra threads.
Of course there are libraries and classes already ready to facilitate this task in all modern languages, such as the class Task
of C#, or even its new constructions async
and await
.
As much as they simplify or "disguise" the use of extra threads, the threads are there, and the usage semantics are the same. What changes is the amount of code you will have to write.
So, to create a C#thread, you can use classic media such as:
...
using System.Threading;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
...
private Thread thread;
private void CorpoDaThread()
{
//Código que será executado em paralelo ao resto do código
}
private void button1_Click(object sender, EventArgs e)
{
//Cria uma nova thread, indicando qual método essa thread deverá executar
thread = new Thread(CorpoDaThread);
//Inicia a execução da thread (em paralelo a esse código)
thread.Start();
}
}
}
You can create threads using lambdas
:
...
using System.Threading;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
...
private Thread thread;
private void button1_Click(object sender, EventArgs e)
{
//Cria uma nova thread, indicando qual método essa thread deverá executar
thread = new Thread(() => {
//Código que será executado em paralelo ao resto do código
});
//Inicia a execução da thread (em paralelo a esse código)
thread.Start();
}
}
}
Or you can use Task
, BackgroundWorker
, async
and await
etc..
The important thing is to keep in mind that on Windows used on PC’s and other operating systems that are not real-time, it is not possible to know with 100% certainty when a thread will start running for real. Only those who know this are the Operating System.
For example, one cannot claim with 100% assurance that the code just below the instruction thread.Start()
will execute or before or afterward of code within this new thread being started.
This is why there are several mechanisms to synchronize execution between competing threads (threads that are running in parallel), which includes concepts such as Critical Section
, Join
, Event
, Semaphore
, Wait
, Sleep
, etc..
This subject is very extensive, and perform operations outside the user interface thread nay is the only reason someone creates extra threads.
You can create threads to split the processing of a task. For example, to search for any element in a cluttered vector, if the computer is able to run more than one thread simultaneously, you can split the search between two threads (each searches for the element in a half of the vector), so that the final time will be approx. half the time the normal search algorithm would take. This can be Fieto with three, four, five or more threads.
In a game, you can split the scene processing between multiple threads, for example, while one thread takes care of the audio, another takes care of the network, and a third of the graphical part.
Anyway, it’s a very long but very interesting subject!
Mastering the concept of threads today is fundamental! It is not enough just to know how to use this or that class/library. I recommend digging deeper into this subject, that the time spent will not be in vain :D
I was thinking of answering but now I have nothing to add.
– Maniero
Thanks, @bigown!
– carlosrafaelgn
@carlosrafaelgn thank you so much for the answer! It helped me a lot to understand the subject. Still, in that reply a question arose, as 5 tasks could be using the same thread?
– Jedaias Rodrigues
@Jedaiasrodrigues is that the
Task
s are controlled internally by the framework, so, if it deems it possible, when aTask
ends, or enters waiting state, he executes anotherTask
in that thread, reusing and optimizing memory– carlosrafaelgn