В чем разница между возвращением void и возвращением задачи?

при просмотре различных образцов C# Async CTP я вижу некоторые асинхронные функции, которые возвращают void и другие, которые возвращают необщего Task. Я понимаю, почему возвращение Task<MyType> полезно возвращать данные вызывающему при завершении асинхронной операции, но функции, которые я видел, имеют тип возврата Task никогда не возвращает никаких данных. Почему бы не вернуться void?

4 ответов


ответы SLaks и Killercam хороши; я думал, что просто добавлю немного больше контекста.

ваш первый вопрос по существу о том, какие методы могут быть отмечены async.

метод, помеченный как async может возвратить void, Task или Task<T>. В чем разница между ними?

A Task<T> возвращение асинхронного метода можно ожидать, и когда задача завершится, он предложит T.

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

A void возвращение асинхронного метода не может быть ожидаемо; это метод" огонь и забыть". Он работает асинхронно, и вы не можете сказать, когда это будет сделано. Это более чем немного странно; как говорит SLaks, обычно вы делаете это только при создании асинхронного обработчика событий. Событие срабатывает, обработчик выполняется; никто не собирается "ждать" задача, возвращаемая обработчиком событий, поскольку обработчики событий не возвращают задачи, и даже если бы они возвращали, какой код использовал бы задачу для чего-то? Обычно это не пользовательский код, который передает управление обработчику в первую очередь.

ваш второй вопрос, в комментарии, по сути, о том, что может быть awaitЭд:

какие методы могут быть awaitЭд? Может ли метод void-returning быть awaitЭд?

нет, пустота-возвращение метод не может быть ожидаемым. Компилятор переводит await M() в вызов M().GetAwaiter(), где GetAwaiter может быть методом экземпляра или методом расширения. Ожидаемое значение должно быть таким, для которого вы можете получить ожидающего; ясно, что метод возврата пустоты не создает значение, из которого вы можете получить ожидающего.

Task - методы возврата могут создавать ожидаемые значения. Мы ожидаем, что третьи стороны захотят создать свои собственные реализации Task-как объекты, которые можно ждать, и вы сможете их дождаться. Однако вам не будет разрешено объявлять async методы, которые возвращают что угодно, кроме void, Task или Task<T>.

(UPDATE: мое последнее предложение может быть фальсифицировано будущей версией C#; есть предложение разрешить возвращаемые типы, отличные от типов задач для асинхронных методов.)

(обновление: функция, упомянутая выше, попала в C# 7.)


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

на самом деле, единственная причина вернуться void Если вы не может возвращение Task потому что вы пишете обработчик событий.


методы возврата Task и Task<T> являются композиционными-это означает, что вы можете await их внутри async метод.

async методы возврата void не являются композиционными, но они имеют два других важных свойства:

  1. их можно использовать как обработчики событий.
  2. они представляют собой асинхронную операцию "верхнего уровня".

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

ASP.NET контекст-один из таких контекстов; если вы используете async Task методы, не ожидая их от асинхронного void метод, затем ASP.NET запрос будет завершен слишком рано.

другой контекст -AsyncContext я написал для модульного тестирования (доступно здесь) - the AsyncContext.Run метод отслеживает выдающийся счет операций и возвращает, когда он равен нулю.


тип Task<T> является типом рабочей лошади параллельной библиотеки задач (TPL), он представляет собой концепцию "некоторая работа/задание, которое будет производить результат типа T в будущем". Понятие "работа, которая будет завершена в будущем, но не возвращает результата" представлено неродовым типом задачи.

именно как результат типа T будет производиться is и детали реализации конкретной задачи; работа может быть распределена на другой процесс на локальной машине, к другому потоку etc. Задачи TPL обычно обрабатываются рабочими потоками из пула потоков в текущем процессе, но эта деталь реализации не является фундаментальной для Task<T> тип; скорее Task<T> может представлять любую операцию с высокой задержкой, которая производит T.

на основе вашего комментария выше:

на await выражение означает "оценить это выражение, чтобы получить объект, представляющий работу, которая будет в будущем дайте результат. Зарегистрируйте остаток текущего метода как обратный вызов, связанный с продолжением этой задачи. Как только эта задача будет выполнена и обратный вызов зарегистрирован, тут вернуть управление моему абоненту". Это противопоставляется / в отличие от обычного вызова метода, что означает: "помните, что вы делаете, запустите этот метод, пока он не будет полностью завершен, а затем выберите, где вы остановились, теперь зная результат метод."


Edit: я должен процитировать статью Эрика Липперта в октябре 2011 года в журнале MSDN, поскольку это было большой помощью для меня в понимании этого материала в первую очередь.

для нагрузок больше информации и whitepages см. здесь.

надеюсь, это поможет.