2015-08-22 3 views
13

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

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

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

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

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 оператора - то отступая в ожидании после того, как это делается ... Но я, наверное, неправильно)

+0

Почему вы делаете 'новый случайным ((INT) DateTime.UtcNow.Ticks)'? Почему бы не просто «new Random()», поскольку это фактически одно и то же. – Enigmativity

+0

Не важно, чтобы это было важно или имело отношение к этому вопросу вообще, но чисто для того, чтобы увидеть, какая, если таковая имеется, разница. Я не заметил никакой разницы в вызове конструктора без параметров, поэтому я просто сделаю это с этого момента. Вы ничего не узнаете, если не попробуете новые вещи. Программирование - это хобби для меня, у меня не было никакого формального образования, кроме Karel the Robot, Pascal и SQL, и это было 13 лет назад, поэтому для меня важно, чтобы я возился, пробовал вещи, ломал вещи, фигурировал их и учиться. – cjk84

+0

Вам будет лучше загружать один из бесплатных декомпиляторов .NET и посмотреть на источник. – Enigmativity

ответ

17

Вы должны избегать использования Task.Factory.StartNew с асинхронным-ОЖИДАНИЕ. Вместо этого вы должны использовать Task.Run.

Асинхронный метод возвращает Task<T>, делегат async также делает. 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); 
+2

Спасибо за объяснение, это имеет смысл. Я отметил это как правильный ответ! – cjk84

+1

@ cjk84 обязательно .. в любое время. – i3arnon

+1

Еще один вопрос для вас: я предполагаю, потому что я удалил async/wait, и в результате задача перешла из задачи > в задачу , она работала исключительно из-за этого и сменилась с Task.Delay на Thread. Сон не имел к этому никакого отношения. Это правильно? – cjk84

0

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

3

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

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

Или:

testTask.Wait(); 
testTask.Result.Wait(); 
+0

А, мне было интересно, почему это не позволит мне добавить ключевое слово async, пока я не вложу задачи. Благодарю вас за информацию! – cjk84

6

Проблема с кодом, что есть две задачи в игре. Один из них - результат вашего вызова 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(); 
+0

Спасибо за ваш ответ! – cjk84

Смежные вопросы