Подтвердить что ты не робот

Task.WaitAll не ждет завершения задачи

При попытке найти новый (возможно, не такой новый, но новый для меня, во всяком случае) асинхронное программирование Task на С#, я столкнулся с проблемой, которая меня немного разобрала, и я не знаю почему.

Я исправил проблему, но я все еще не уверен, почему это было проблемой для начала. Я просто подумал, что поделюсь своим опытом, если кто-то там столкнется с той же ситуацией.

Если какие-либо гуру хотели бы сообщить мне о причине проблемы, это было бы замечательно и высоко оценено. Мне всегда нравится знать, почему что-то не работает!

Я выполнил тестовую задачу следующим образом:

Random rng = new Random((int)DateTime.UtcNow.Ticks);
int delay = rng.Next(1500, 15000);
Task<Task<object>> testTask = Task.Factory.StartNew<Task<object>>(
    async (obj) =>
        {
            DateTime startTime = DateTime.Now;
            Console.WriteLine("{0} - Starting test task with delay of {1}ms.", DateTime.Now.ToString("h:mm:ss.ffff"), (int)obj);
            await Task.Delay((int)obj);
            Console.WriteLine("{0} - Test task finished after {1}ms.", DateTime.Now.ToString("h:mm:ss.ffff"), (DateTime.Now - startTime).TotalMilliseconds);
            return obj;
        },
        delay
    );
Task<Task<object>>[] tasks = new Task<Task<object>>[] { testTask };

Task.WaitAll(tasks);
Console.WriteLine("{0} - Finished waiting.", DateTime.Now.ToString("h:mm:ss.ffff"));

// make console stay open till user presses enter
Console.ReadLine();

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

6: 06: 15.5661 - Запуск тестовой задачи с задержкой 3053 мс.
6: 06: 15.5662 - Завершение ожидания.
6: 06: 18.5743 - тестовое задание завершено после 3063.235мс.

Как вы можете видеть, оператор Task.WaitAll(tasks); мало что сделал. Он ждал всего 1 миллисекунду, прежде чем продолжить выполнение.

Я ответил на свой "вопрос" ниже - но, как я сказал выше, - если кто-нибудь более осведомлен, чем я хотел бы объяснить, почему это не работает, сделайте это! (Я думаю, что это может иметь какое-то отношение к выполнению "пошагового" метода после того, как он достигает оператора await, а затем отступит назад, как только ожидание будет выполнено... Но я, вероятно, ошибаюсь)

4b9b3361

Ответ 1

Вам следует избегать использования Task.Factory.StartNew с асинхронным ожиданием. Вместо этого следует использовать Task.Run.

Асинхронный метод возвращает a Task<T>, также делает делегат async. Task.Factory.StartNew также возвращает Task<T>, где его результат является результатом параметра делегата. Поэтому при использовании вместе возвращает a Task<Task<T>>>.

Все, что делает этот Task<Task<T>>, - это выполнить делегат, пока не будет возвращена задача, которая будет, когда будет достигнута первая ожидание. Если вы только дождались завершения этой задачи, вы не дожидаетесь всего метода, просто часть до первого ожидания.

Вы можете исправить это, используя Task.Unwrap, который создает Task<T>, который представляет это Task<Task<T>>>:

Task<Task> wrapperTask = Task.Factory.StartNew(...);
Task actualTask = wrapperTask.Unwrap();
Task.WaitAll(actualTask);

Ответ 2

Проблема с вашим кодом заключается в том, что в игре есть две задачи. Один из них является результатом вашего вызова Task.Factory.StartNew, который заставляет анонимную функцию выполняться в пуле потоков. Однако ваша анонимная функция, в свою очередь, скомпилирована для создания вложенной задачи, представляющей завершение ее асинхронных операций. Когда вы ждете своего Task<Task<object>>, вы только ждете от внешней задачи. Чтобы ждать внутренней задачи, вы должны использовать Task.Run вместо Task.Factory.StartNew, так как она автоматически разворачивает вашу внутреннюю задачу:

Random rng = new Random((int)DateTime.UtcNow.Ticks);
int delay = rng.Next(1500, 15000);
Task<int> testTask = Task.Run(
    async () =>
    {
        DateTime startTime = DateTime.Now;
        Console.WriteLine("{0} - Starting test task with delay of {1}ms.", DateTime.Now.ToString("h:mm:ss.ffff"), delay);
        await Task.Delay(delay);
        Console.WriteLine("{0} - Test task finished after {1}ms.", DateTime.Now.ToString("h:mm:ss.ffff"), (DateTime.Now - startTime).TotalMilliseconds);
        return delay;
    });
Task<int>[] tasks = new[] { testTask };

Task.WaitAll(tasks);
Console.WriteLine("{0} - Finished waiting.", DateTime.Now.ToString("h:mm:ss.ffff"));

// make console stay open till user presses enter
Console.ReadLine();

Ответ 3

Здесь Task.WaitAll ждет внешнюю задачу, а не внутреннюю задачу. Используйте Task.Run, чтобы не иметь вложенных задач. Это решение наилучшей практики. Другим решением является также ожидание внутренней задачи. Например:

Task<object> testTask = Task.Factory.StartNew(
    async (obj) =>
        {
            ...
        }
    ).Unwrap();

Или:

testTask.Wait();
testTask.Result.Wait();

Ответ 4

После долгих обвинений и вытягивания волос я, наконец, решил избавиться от асинхронной лямбды и просто использовал метод System.Threading.Thread.Sleep, чтобы понять, не изменилось ли это.

Новый код завершился следующим образом:

Random rng = new Random((int)DateTime.UtcNow.Ticks);
int delay = rng.Next(1500, 15000);
Task<object> testTask = Task.Factory.StartNew<object>(
    (obj) =>
        {
            DateTime startTime = DateTime.Now;
            Console.WriteLine("{0} - Starting test task with delay of {1}ms.", DateTime.Now.ToString("h:mm:ss.ffff"), (int)obj);
            System.Threading.Thread.Sleep((int)obj);
            Console.WriteLine("{0} - Test task finished after {1}ms.", DateTime.Now.ToString("h:mm:ss.ffff"), (DateTime.Now - startTime).TotalMilliseconds);
            return obj;
        },
        delay
    );
Task<object>[] tasks = new Task<object>[] { testTask };

Task.WaitAll(tasks);
Console.WriteLine("{0} - Finished waiting.", DateTime.Now.ToString("h:mm:ss.ffff"));

// make console stay open till user presses enter
Console.ReadLine();

Примечание. Из-за удаления ключевого слова async из метода лямбда тип задачи теперь может быть просто Task<object>, а не Task<Task<object>> - вы можете увидеть это изменение в приведенном выше коде.

И, вуаля! Это сработало! Я получил "Завершенное ожидание". сообщение ПОСЛЕ завершения задачи.

Увы, я помню, где-то читал, что вы не должны использовать System.Threading.Thread.Sleep() в коде Task. Не могу вспомнить, почему; но поскольку это было просто для тестирования, и большинство задач фактически будут делать что-то, что требует времени, а не притворяется, что он делает что-то, что требует времени, это не должно быть проблемой.

Надеюсь, это поможет некоторым людям. Я определенно не лучший программист в мире (или даже близко), и мой код, вероятно, невелик, но если он помогает кому-то, отлично!:)

Спасибо за чтение.

Изменить: Другой ответ на мой вопрос объясняет, почему у меня была проблема, которую я сделал, и этот ответ только решил проблему по ошибке. Переход к Thread.Sleep(x) не повлиял. Спасибо всем, кто ответил и помог мне!