Кто-нибудь может объяснить, почему этот код просто заходит в тупик после срабатывания WhenAll?

Основной код:

class AsyncTests
{
    public async void Start()
    {
        Console.WriteLine($"Thread:{Thread.CurrentThread.ManagedThreadId} - starting whole process, calling await DoWork1()");
        await Task.WhenAll(DoWork1(), DoWork2());
        Console.WriteLine($"Thread:{Thread.CurrentThread.ManagedThreadId} - finished awaiting DoWork1 and DoWork2");
    }

    public async Task DoWork1()
    {
        Console.WriteLine($"Thread:{Thread.CurrentThread.ManagedThreadId} - starting DoWork1");
        await DoNothing();
        Console.WriteLine($"Thread:{Thread.CurrentThread.ManagedThreadId} - finished DoWork1");
    }

    public async Task DoWork2()
    {
        Console.WriteLine($"Thread:{Thread.CurrentThread.ManagedThreadId} - starting DoWork2");
        await DoNothing();
        Console.WriteLine($"Thread:{Thread.CurrentThread.ManagedThreadId} - finished DoWork2");
    }

    public Task DoNothing() { return new Task(() => { /* do nothing */ }); }
}

Код управления программой:

    static void Main(string[] args)
    {            
        var x = new AsyncTests();
        Console.WriteLine($"Thread:{Thread.CurrentThread.ManagedThreadId} - Main ... calling Start()");
        Task.Run(() => x.Start());
        Console.WriteLine($"Thread:{Thread.CurrentThread.ManagedThreadId} - Main ... start is running");
        Console.ReadKey();
    }

Выход:

Thread:1 - Main ... calling Start()
Thread:1 - Main ... start is running
Thread:4 - starting whole process, calling await DoWork1()
Thread:4 - starting DoWork1
Thread:4 - starting DoWork2

ОБНОВЛЕНИЕ

Чтобы сделать это немного понятнее, давайте изменим его так, чтобы DoNothing фактически вызывал Thread.Sleep (2000), и моя цель - запустить два спящих потока одновременно и хотеть использовать шаблон async / await для достижения этой цели.

Если я изменю «DoNothing» на асинхронную задачу, которая выполняет спящий режим, то мне скажут, что там нужны операторы await. Что означает, что мне нужно написать еще один async метод, которого следует ожидать. Итак, как лучше всего завершить эту цепочку вызовов с точки зрения операторов?

Может ли кто-нибудь показать пример передового опыта того, как достичь вышеуказанного?

0
James Harcourt 30 Апр 2019 в 19:56

3 ответа

Лучший ответ

Вы никогда не начинаете свою задачу в DoNothing.

https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task?view=netframework-4.8#remarks

Разделение создания и выполнения задач

Класс Task также предоставляет конструкторы, которые инициализируют задачу, но не планируют ее выполнение. По соображениям производительности метод Task.Run или TaskFactory.StartNew является предпочтительным механизмом для создания и планирования вычислительных задач, но для сценариев, где создание и планирование должны быть разделены, можно использовать конструкторы, а затем вызвать метод Task.Start для планирования задача для выполнения в более позднее время.

4
Lesiak 30 Апр 2019 в 17:10

Вместо того, чтобы создавать задание для возврата, пусть язык сделает это за вас.

public async Task DoNothing() { }

Вышеприведенное фактически ничего не делает и вернет Task, который находится в завершенном состоянии и его можно ожидать.

То, как вы сейчас это делаете, Задача создается, но она никогда не запускается и не устанавливается в Завершено, поэтому ожидание ее навсегда заблокирует программу.

0
John Wu 30 Апр 2019 в 17:39

Просто чтобы дополнить уже предоставленные ответы и комментарии, я хотел показать пример кода, работающий так, как я задумал в тесте. Он показывает поток выполнения + для информации, идентификатор управляемого потока выполнения в определенные моменты времени.

Основной код:

class AsyncTests
{
    public async Task StartAsync()
    {
        Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff} - Thread:{Thread.CurrentThread.ManagedThreadId} - starting whole process, calling await DoWork1Async()");
        await Task.WhenAll(DoWork1Async(), DoWork2Async());
        Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff} - Thread:{Thread.CurrentThread.ManagedThreadId} - finished awaiting DoWork1Async and DoWork2Async");
    }

    public async Task DoWork1Async()
    {
        Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff} - Thread:{Thread.CurrentThread.ManagedThreadId} - starting DoWork1Async");
        await Sleep();
        Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff} - Thread:{Thread.CurrentThread.ManagedThreadId} - finished DoWork1Async");
    }

    public async Task DoWork2Async()
    {
        Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff} - Thread:{Thread.CurrentThread.ManagedThreadId} - starting DoWork2Async");
        await Sleep();
        Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff} - Thread:{Thread.CurrentThread.ManagedThreadId} - finished DoWork2Async");
    }        

    public async Task Sleep()
    {
        await Task.Delay(2000);
    }
}

Вызывающий код (обратите внимание, что для выполнения этой операции асинхронно, мне пришлось опустить оператор await, который вызывает предупреждение consider applying the 'await' operator при вызове метода StartAsync().

static void Main(string[] args)
{            
    var x = new AsyncTests();
    Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff} - {Thread.CurrentThread.ManagedThreadId} - Main ... calling Start()");
    x.StartAsync();
    Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff} - {Thread.CurrentThread.ManagedThreadId} - Main ... start is running");
    Console.ReadKey();
}

Наконец, вывод - который, как и ожидалось, показывает выполнение кода / управление, возвращающееся в места, ожидаемые для действительно асинхронной операции. Как и ожидалось, для запуска спящего режима использовались два разных потока пула.

10:43:36.515 - 1 - Main ... calling Start()
10:43:36.546 - Thread:1 - starting whole process, calling await DoWork1Async()
10:43:36.547 - Thread:1 - starting DoWork1Async
10:43:36.561 - Thread:1 - starting DoWork2Async
10:43:36.562 - 1 - Main ... start is running
10:43:38.581 - Thread:4 - finished DoWork2Async
10:43:38.582 - Thread:5 - finished DoWork1Async
10:43:38.582 - Thread:5 - finished awaiting DoWork1Async and DoWork2Async
0
James Harcourt 1 Май 2019 в 09:50