Почему шаблон асинхронного ожидания в этом консольном приложении вызвал тупик? - PullRequest
0 голосов
/ 30 апреля 2019

Кто-нибудь может объяснить, почему этот код просто заходит в тупик после срабатывания 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 метод, которого следует ожидать.Итак, как лучше всего завершить эту цепочку вызовов с точки зрения операторов?

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

Ответы [ 3 ]

4 голосов
/ 30 апреля 2019

Вы никогда не запускаете задачу в 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 для планированиязадача для выполнения в более позднее время.

0 голосов
/ 01 мая 2019

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

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

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);
    }
}

Код вызова (обратите внимание, что для выполнения этого асинхронно выполняется Iпришлось опустить оператор 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 голосов
/ 30 апреля 2019

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

public async Task DoNothing() { }

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...