Understanding threads in C#

Asked

Viewed 19,104 times

29

I’m trying to figure out how to use Tasks and asynchronous methods in C#, but every place I see about it in internet mentions a "usual multithreading" form that would be different from the use of tasks.

The problem is that so far I don’t quite understand how the threads, I just know that they would intuitively be "code execution paths".

That way, how to understand correctly threads in C#? What they are and how we work with these threads? I believe that question is analogous à that one which I did, but considering now the relationship with the processor and not with memory.

3 answers

38


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:

Threads

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:

Botão salvar

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

  • 5

    I was thinking of answering but now I have nothing to add.

  • Thanks, @bigown!

  • @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?

  • 1

    @Jedaiasrodrigues is that the Tasks are controlled internally by the framework, so, if it deems it possible, when a Task ends, or enters waiting state, he executes another Task in that thread, reusing and optimizing memory

8

You spoke of "asynchronous". Let me ask you: you know the technical difference of an asynchronous process to a synchronous process?

Basically, the synchronous process happens with competition; the asynchronous does not possess that need by itself - I can tell you something without you expecting to hear.

A very nice example that I read in the SOPT goal - I don’t remember the author - is the phone case: we say "hello?" hoping to get an answer so that only thus we continue the conversation process; when the conversation ends, for confirmation of such fact, we say goodbye with a "goodbye" or, simpler still, with a "bye!" - this is all to ensure that the competition is aware of the subsequent action.


As threads are ways to take or split information from a source point to a destination within a stream. For example, you want to save string in memory. To do this, one or more threads are responsible so that the task of "save" be completed successfully.

Remember what we talked about synchronous and asynchronous?

Well, you mentioned the "multithreading" thing: this guy is asynchronous, which means that you can have multiple parallel threads that share the responsibility for the tasks assigned to them. In other words, asynchronous and multiple threads can perform operations without the need for a previously executed thread to be finally completed.

Synchronous threads perform only one operation at a time and none is triggered while the previous one is being processed.

Piscar and escovar os dentes is perfectly possible and are asynchronous and independent processes. In this case, we would have a trivial example of multithreading.

Now, escovar os dentes and assoviar/assobiar operates on a thread only - one process needs to be stopped/completed before the other starts.

  • 3

    I was the one who said that at goal :) http://meta.pt.stackoverflow.com/a/851

  • 1

    Wouldn’t it be brushing your teeth and whistling? I can blink and whistle simultaneously :)

  • 1

    @That. Brush your teeth and whistle. But in fact, I would use the example of sucking cane and whistling, but then I confused the balls and ended up making a total mistake. Thank you for the touch!

0

A Task in C#, in its simplest form, being it synchronous is nothing more than a piece of code that runs in parallel with the main stream, ie a thread.
Your main process or Taks controller, can be another thread including, can launch Tasks to perform tasks according to activity demand. Each Task can perform its task and terminate or it can wait for more activities to be executed. This makes the system more efficient as the process of creating a thread is costly, so reusing a Taks is better.
The biggest problem with threading is having to control access to shared resources, so only one thread has access control at a time.
Asynchronous tasks should be used primarily with events to signal that your activity has been completed.
Developing complex applications using asynchronous access and execution controls can be very attractive, but requires great experience in development. Years of development have shown that the most efficient solutions are not the ones using the most complex forms of programming.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.