it-roy-ru.com

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

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

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

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

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

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, а затем, как только будет выполнено ожидание, вернуться назад ... Но я, вероятно, ошибаюсь) 

14
cjk84

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

Асинхронный метод возвращает Task<T>, а асинхронный делегат - тоже. Task.Factory.StartNew также возвращает Task<T>, где его результат является результатом параметра делегата. Таким образом, при совместном использовании он возвращает 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);
19
i3arnon

Проблема с вашим кодом в том, что в игре есть две задачи. Одним из них является результат вашего вызова Task.Factory.StartNew, который вызывает выполнение анонимной функции в пуле потоков. Однако ваша анонимная функция в свою очередь компилируется для создания задачи nested, представляющей завершение ее асинхронных операций. Когда вы ожидаете своего Task<Task<object>>, вы ожидаете только задания outer. Чтобы дождаться выполнения внутренней задачи, вы должны использовать 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();
9
Douglas

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

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

Или же:

testTask.Wait();
testTask.Result.Wait();
3
usr

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

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

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

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

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

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

0
cjk84