Keys to unraveling and effectively mastering the topics Task, Synchronicity, Asynchrony

When mastering new knowledge, we, one way or another, try to connect it with already mastered knowledge, and therefore, when studying already complex abstractions, the terms of which have their own special meaning, we often reach a dead end.

This situation occurs when a novice programmer gets acquainted with the topics Task, Synchronicity, Asynchrony. The situation is aggravated by the fact that often more experienced colleagues use expressions from the category “Synchronous/Asynchronous execution of a task” in jargon.

In order to unravel this “Gordian Knot,” let’s approach the problem from afar: consider it using the example of the work of a symphony orchestra. Let us compare the meanings of problematic terms from the point of view of people with different professional orientations.

We will involve two experts to carry out the analysis.

Meet Valentin. Valentin is a philologist by training. He is well versed in terms.

And this is Alexey. He no longer remembers who he studied for, because by the 2nd year he became a senior, he grew a beard, and he forgot about the university. He is also well versed in terms.

E:\DeX Report\Material for the article\Jun.bmp

And this is a novice programmer Vanka. Vanka doesn’t yet really understand what his senior development colleagues are talking about, but he is confident that he will figure it out, since over the course of his 30 years he has used a lot of terms, putting into them approximately the same meaning as Valentin.

Let's start with the analysis.

Parallel execution

From Valentin’s point of view, the parallel execution of any operations using the example of an orchestra’s work looks like this: the conductor instructs two performers (Violinist 1 and Violinist 2) to begin performing an excerpt from the play. Each of them starts the game, but it is not at all necessary that they start playing at the same time, nor is it necessary that they complete the passage at the same time. It is not even necessary that they will be in time. The term “parallel” means only that at a given moment in time both violinists are playing the violin. It is possible that one of them completely mixed up the notes; parallelism does not cancel this. (Fig. 1)

Rice.  1

Rice. 1

If we ask Alexey what he thinks about this, he will say that the two methods can be executed in parallel if we run them in different threads (two fiddlers). However, we cannot guarantee that the execution of these methods will end at the same time. This is clearly visible when the logs of the work performed by violinists are displayed on the console (see Fig. 2). Because threads can be loaded with work other than what we assign to them, our methods output data to the screen in an unpredictable order. It’s as if both violinists are complete beginners, it’s very difficult for them to keep the tempo and they can’t work as one team (see Fig. 1). We can only say that each of them is playing now.

Rice.  2

Rice. 2

Well, here the experts’ views coincided. Linguists and programmers understand the same thing by the term “parallel”.

The concepts of synchrony and asynchrony, which will be discussed later, are most often used in relation to a task, not a thread, so before moving on to the analysis of the next term, let's introduce a new abstraction – Task. A task is to a thread in the same way that playing a violin is to a violinist. Potok is a musician, a violinist in our case (don’t forget about the conductor – he is also a musician). The task he performs (playing the violin) is that very Task (see Fig. 3).

Rice.  3

Rice. 3

A task cannot be performed without a thread, just as a violin cannot play without a violinist. But because all the work of allocating a thread to perform a new task is hidden under the hood of the Task abstraction, we can treat tasks as self-contained entities without thinking about threads. Just like a conductor does in an orchestra. He doesn’t care who is on stage today – violinist Petrov or violinist Ivanov, for him it’s a “violin”, he says to her: “Play,” and not to Petrov.

Synchronous execution

Rice.  4

Rice. 4

Valentin claims that synchronously – this is when the conductor waved his baton, and both violins began to play at the same time, they play professionally, and hit the notes, and both have the same tempo of play. If you stand between them, a stereoscopic effect will appear, as if the same melody is coming from two headphones. Pro violins (see Fig. 4).

However, if we ask Alexey to tell us about synchronous execution of tasks, he will ask us: “Which one are you talking about, parallel or sequential?” O_o

As paradoxical as it may seem for a linguist, in everyday programmer slang we can find two variations of the term “Synchronous execution” in relation to sequential and parallel running of tasks. However, the statement “Tasks are performed synchronously…” is not correct, it does not reflect the meaning of what is happening and misleads beginners who are just getting acquainted with the abstraction in question.

Applied to implementation There is no program synchronicity. Tasks can be executed either sequentially or in parallel, but not synchronously.

Let's consider an example – let the conductor (the thread in which our Main method is executed) launch 2 violins. After which we wrote a call to the Wait method to wait for the completion of each of the running tasks. The violins have started and are working in parallel, while the conductor is blocked on the line calling Wait and waiting for the violins to complete their work.

 static void Main(string[] args)
        {
            Console.WriteLine("Дирижер дал указания скрипкам");

            Task violin1 = Task.Run(() => Print("Скрипка 1 закончила игру"));
            Task violin2 = Task.Run(() => Print("Скрипка 2 закончила игру"));

            violin1.Wait(); //блокирующее ожидание завершения задачи violin1
            violin2.Wait(); //блокирующее ожидание завершения задачи violin2

            Console.WriteLine("Дирижер готов раздавать новые указания");
        }

        static void Print(string text)
        {            
            Thread.Sleep(5000);
            Console.WriteLine(text);
        }
Rice.  5

Rice. 5

The current situation can be described as “Synchronous expectation completing tasks.” Please note, there is a very important point here, we are talking about synchronicity in relation to the process expectations execution (in other words, completion), and not to the execution process itself. In this case, tasks are performed in parallel, as when considering parallelism. It is in parallel, with all the chaos inherent in parallelism in the order of data output to the monitor (see Fig. 5 and Fig. 2). That is, again two inexperienced violinists are torturing the violins, each in their own rhythm, as in Figure 1, there is no synchrony in their work.

We can place a task completion wait command after each task starts, with the conductor waiting for one violin to finish playing before instructing the second violin to play. In this case, the work will be performed sequentially, one Task after another (see Fig. 6).

  static void Main(string[] args)
        {
            Console.WriteLine("Дирижер дал указания скрипке 1");

            Task violin1 = Task.Run(() => Print("Скрипка 1 закончила игру"));       
            violin1.Wait(); //блокирующее ожидание завершения задачи violin1

            Console.WriteLine("Дирижер дал указания скрипке 2");

            Task violin1 = Task.Run(() => Print("Скрипка 2 закончила игру"));      
            violin2.Wait(); //блокирующее ожидание завершения задачи violin2

            Console.WriteLine("Дирижер готов раздавать указания");
            Console.ReadLine();
        }
Rice.  6

Rice. 6

In everyday life, you can hear that this is “sequential, synchronous execution,” but this is also an error. Since the term “synchronously” does not apply to implementation program, it would be correct to say that this is “synchronous expectation execution..” or “synchronous expectation completion…”, “blocking expectation” That is, by synchrony we mean precisely the fact of blocking of the conductor (the thread that launched the task).

“Nothing is clear, but very interesting. Well, okay, where is the beautiful playing of two violins?”

It's a paradox, but it won't happen. The chaos described above is hidden behind the screen of the term “synchronously” in relation to the execution of program code. The terms “synchronously” and “with blocking of the starting thread” are used here as synonyms, although this is not obvious. This must be clearly understood when you try to dive into this topic. Expecting the opposite may prevent you from mastering this tool.

What to believe now? How to rely on life experience? So what about asynchrony then? Much more asynchronous than what is described in this paragraph?

Asynchronous execution

If we ask Valentin what he thinks about the term “asynchronously”he will say:

Rice.  7

Rice. 7

Well, imagine two violins who decided to play a trick on the conductor. They started at the same time, kept the same tempo of playing, but not a single note in their playing matched. It was as if they were playing different pieces (see Fig. 7).

When the first violin plays A, the second plays B. When the first violin plays B, the second plays A. And so on throughout the piece.

Alexey claims that, from the point of view of the violin, asynchronous behavior is not much different from the case that we described when considering synchronous work.

In the case of sequential launch of tasks, the first violin has played, the second is instructed to start working, etc. (see Fig. 8). But there is one thing! The conductor is freed as soon as he gives instructions to the first violin, and is free until the violin completes its work. After this he is asked to return to conducting, he gives instructions and is free again while a new task is carried out.

 async static Task Main(string[] args)
        {
            Console.WriteLine("Дирижер дал указания скрипке 1");
            Task violin1 = PrintAsynс("violin1");
            Console.WriteLine("Дирижер пошел по своим делам");
            await violin1; //не блокирующее ожидание завершения задачи violin1


            Console.WriteLine("Дирижер дал указания скрипке 2");
            Task violin2 = PrintAsynс("violin2");
            Console.WriteLine("Дирижер пошел по своим делам");
            await violin2; //не блокирующее ожидание завершения задачи violin2

            Console.WriteLine("Дирижер готов раздавать новые указания");
        }

        static async Task PrintAsynс(string text)
        {
            Console.WriteLine($"Скрипка {text} играет");
            await Task.Delay(10000);
            Console.WriteLine($"Скрипка {text} закончила игру");
        }
Rice.  8

Rice. 8

We can say that for the violin there is neither synchrony nor asynchrony. The terms synchronous/asynchronous apply only to the conductor.

So how? For what? Why? What does asynchrony have to do with it? You're all lying! This all makes no sense! This doesn't happen in programming! Mathematical order reigns everywhere there!

And like this. Although this has nothing to do with what Valentin means by asynchrony, this approach is called asynchronous. The phrase “Asynchronous execution of tasks” is often mistakenly used. This process can be most accurately described by the phrase “Asynchronous waiting for a task to complete,” or better yet, “non-blocking waiting for a task to complete.”

This mode of operation provides great opportunities in terms of efficient use of resources. Let's assume that we have only one thread at our disposal. A sort of man-orchestra represented by a conductor. He comes out to the audience, bows, turns to the music stand, waves his baton, pointing at the violin, and… throws the baton, picks up the violin. He plays the violin part, runs back to the music stand, instructs the piano to start playing and carries out these instructions himself (see Fig. 9).

Rice. 9

In synchronous mode, the conductor simply stands, waits for the next instrument to complete its work, and the moment comes to make the next swing of the baton. But the investigator cannot run around and do the work for the entire orchestra.

It is worth understanding: despite the fact that after the asynchronous method is launched, the thread is released, the execution of the method in which the asynchronous task was launched (in our case, the Main method), while Not continues, it stops at the await statement and waits for the result of the task. Yes, the thread can be involved in other work at this time, but the program code in the Main method is frozen on the line with await and is waiting for the result! When the result arrives, some thread will be attracted to further execution of the method (not necessarily the same one that executed the Main method before; for ease of explanation, we deliberately omit this point).

When studying the literature on this topic, you may come across the following phrase: “While an asynchronous task is running, code execution returns to the calling method – that is, in our case, to the method Main” Avoid the misconception that this will lead to further execution of the method Main! In the following example, the method works Main continue only after it – the method – has waited for the result of all asynchronous tasks.

Here, the use of the expression “non-blocking wait” instead of “asynchronously” perfectly conveys the meaning of what is happening – we are waiting for the result, the code is not executed!

It’s like a conductor who is allowed to go have tea while the violin plays a part, but is not allowed to give instructions to other instruments at this time. It's not too early, according to the notes.

I don't understand. >_> If the conductor is free, why can't he give instructions to other instruments?

And no one said that he couldn’t. It’s just that in the case described this was the task. In the notes of the piece it was written: first the violin part plays, after which the cello enters. So the poor conductor was toiling around with nothing to do, he didn’t seem to be busy, and he couldn’t even wave his baton yet. The await operator told him: “Do something else, I’ll call you when the violin plays.”

OK. What if we take another piece in which we need to use 2 violins at once?

In this case, we would rewrite the code as follows:

 async static Task Main(string[] args)
        {
            Console.WriteLine("Дирижер дал указания скрипке 1");

            Task violin1 = PrintAsynс("violin1");
            Task violin2 = PrintAsynс("violin2");

            await violin1; //не блокирующее ожидание завершения задачи violin1
            await violin2; //не блокирующее ожидание завершения задачи violin2

            Console.WriteLine("Дирижер готов раздавать новые указания");
        }

        static async Task PrintAsynс(string text)
        {
            Console.WriteLine($"Скрипка {text} играет");
            await Task.Delay(10000);
            Console.WriteLine($"Скрипка {text} закончила игру");
        }

In this case, our conductor will first start the work of two violins and go about his business until the violins complete their work. The violins are manipulated by two independent violinists (two separate threads), so the conductor is free. And, of course, we cannot guess the order in which they are completed, as in the previously discussed cases of parallel execution, we can only say that we will first wait until the first violin finishes playing, and then, if the second one does not finish playing, we will wait for it too (see . order of await operators).

Wait a minute, you're deceiving me again! We've been through this before! How does this differ from synchronous-parallel execution? It was the same there!

Perhaps, from the point of view of violins and violinists, the situation is very similar, and this can create grounds for confusion. But there is one thing: in the synchronous version, in order to wait for the violins to finish working, we forced the conductor to wait (see Fig. 5, Fig. 7). He could not distract himself from the process of the violins working. In asynchronous mode, we tell him: “You can still go have some tea, the violins won’t finish soon anyway. And when they’re finished, we’ll call you ourselves.”

Remember the previous example, when the conductor worked for the entire orchestra? If there are other performers in the orchestra besides the conductor (other free streams), then he does not have to play the parts of all instruments. He gave instructions to two violinists; the await operator prohibits him from conducting further, but the orchestra has plenty of other work to do. He can still sit down at the piano.

Same with threads and tasks. It is only worth clarifying that we do not know what work our thread will do. In this case, we are not involved in the distribution of work. Just as we cannot be sure that it is the conductor thread that is busy performing the piano task. By using an asynchronous approach, we only ensure that the conductor does not waste the stage. He will receive the status “free” and will become available for other work. But most often we don’t care who holds the violin in his hands – Petrov, Ivanov or the conductor himself.

And by the way, you again make a mistake by using the phrase “..synchronous-parallel execution”, the correct word would be “synchronous waiting for execution” and it doesn’t matter what kind of work we expect – parallel or sequential.

In addition to the examples described above, there may be cases that at first glance are difficult to classify as synchronous or asynchronous.

For example, if we simply write in the Main method to launch several Tasks, this will be equivalent to the conductor giving instructions to the violinists, looking at his watch, seeing that the working day was over, turning around and leaving without finishing listening to the game or, in technical language, the Main method will complete its work before the tasks it started. Consequently, we will not see the result of their execution (see Fig. 10).

async static Task Main(string[] args)
        {
            Console.WriteLine("Дирижер дал указания скрипкам");

            Task violin1 = Task.Run(() => Print("Скрипка 1 закончила игру"));
            Task violin2 = Task.Run(() => Print("Скрипка 2 закончила игру"));

            Console.WriteLine("Дирижер готов раздавать новые указания");
        }

        static void Print(string text)
        {
            Thread.Sleep(5000);
            Console.WriteLine(text);
        }
Rice.  10

Rice. 10

There seems to be no blocking of the running thread – therefore we can assume that this is an asynchronous case. But there is also no waiting through the await operator, therefore, this is not asynchrony. This example should be classified simply as parallel execution of the program. The concept of synchrony and asynchrony does not apply to it.

In general, I understood everything. There is no synchronicity in your synchronicity, just some kind of parallelism and consistency. Asynchrony is also not about asynchrony. And they differ from one another, in general, only in the efficiency of the work of the poor conductor… You know, I will probably become a musician while my nervous system is still with me.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *