S
S
Sergey2022-03-18 21:09:46
C++ / C#
Sergey, 2022-03-18 21:09:46

Explain why this method works the way it does?

I immediately apologize for the inaccurate wording of the question, but I could not formulate it in any specific way. Asynchronous question. I immediately give the code, I think for an average programmer it is more than understandable, but I don’t quite understand the result of its output.

Here is the code:

class Program
    {
        static Random random = new Random();

        static void Main(string[] args)
        {
            Console.WriteLine("Сколько запросов отправить?");
            int count = int.Parse(Console.ReadLine());
            Task<string>[] taskMas = new Task<string>[count];


            for (int i = 0; i < count; i++)
            {
                taskMas[i] = Task.Run<string>(() => GetData(i));
            }
            Console.WriteLine("Все задачи отправлены");

            Task.WaitAll(taskMas);
            foreach(Task<string> task in taskMas)
            {
                Console.WriteLine(task.Result);
            }

            Console.WriteLine("Программа выполнилась");
        }
    
        static string GetData(int id)
        {
            int delay = random.Next(0, 1500);
            int myId = id;
            Thread.Sleep(delay);
            return $"Запрос {myId} выполнялся {delay}";
        }
    }


There are, as it were, queries that can be executed at random times. At the same time, I do not wait until each request is completed, but I get how it is convenient to represent it - "obligations" to complete each request, i.e. methods that are issued immediately. Then I wait using the WaitAll method when all the obligations (Task) are fulfilled and I request the result of the work from each of them.

Here is the output of the program:
Сколько запросов отправить?
10
Все задачи отправлены
<Вот тут идет пауза пока методы выполняются>
Запрос 10 выполнялся 162
Запрос 10 выполнялся 171
Запрос 10 выполнялся 1267
Запрос 10 выполнялся 307
Запрос 10 выполнялся 184
Запрос 10 выполнялся 722
Запрос 10 выполнялся 762
Запрос 10 выполнялся 101
Запрос 10 выполнялся 1424
Запрос 10 выполнялся 1044
Программа выполнилась


It looks like some banal thing here that I can't understand, why are the id's the same? I kind of already and store them in a separate variable in the method, but they still equal the last identifier with which this method was called. But it is embarrassing that the delay worked absolutely honestly and it is different for all requests. Can you tell me what I did wrong here?

Answer the question

In order to leave comments, you need to log in

1 answer(s)
F
freeExec, 2022-03-18
@Quttar72

Because in fact you do not pass the value of the counter as an argument (the method is not immediately executed), you "capture" a variable that will work somewhere later. So by the time it comes down to it, the counter is already equal to 10, and you see it.
Alternatively, for example, you can add an intermediate variable j, which you equate to i, and give it away. In general, you need to create a separate structure where you add the parameters for the task and give it back there.
P.S.
If you look at what actually comes out after compilation

private sealed class <>c__DisplayClass0_0
    {
        public int i;

        internal string <Main>b__0()
        {
            return GetData(i);
        }
    }

    public static void Main()
    {
        int num = 5;
        Task<string>[] array = new Task<string>[num];
        <>c__DisplayClass0_0 <>c__DisplayClass0_ = new <>c__DisplayClass0_0();
        <>c__DisplayClass0_.i = 0;
        while (<>c__DisplayClass0_.i < num)
        {
            array[<>c__DisplayClass0_.i] = Task.Run(new Func<string>(<>c__DisplayClass0_.<Main>b__0));
            <>c__DisplayClass0_.i++;
        }
    }

then you can see that the loop counter is a field of a hidden class that has only 1 instance, which is created before the loop. And our delegate for taxes reads the value of this field when it is its turn to execute. By that time the counter reaches the end.
If we create an intermediate variable, then in the code, for each dachshund, its own individual instance of the hidden class is created, so the value will be the one that was.
int num2 = 0;
        while (num2 < num)
        {
            <>c__DisplayClass0_0 <>c__DisplayClass0_ = new <>c__DisplayClass0_0();
            <>c__DisplayClass0_.j = num2;
            array[num2] = Task.Run(new Func<string>(<>c__DisplayClass0_.<Main>b__0));
            num2++;
        }

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question