Какая разница между возвращением пустоты и возвратом задачи?

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

Ответ 1

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

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

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

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

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

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

Второй вопрос, в комментарии, в основном о том, что может быть await ed:

Какие методы могут быть await ed? Может ли метод возврата void быть await ed?

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

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

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

(ОБНОВЛЕНИЕ: упомянутая выше функция сделала это на С# 7.)

Ответ 2

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

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

Ответ 3

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

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

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

Вторая точка важна, когда вы имеете дело с контекстом, который поддерживает количество выдающихся асинхронных операций.

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

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

Ответ 4

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

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

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

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


Изменить: я должен привести статью Эрика Липперта в октябре 2011 года в журнале MSDN Magazine, поскольку это очень помогло мне в понимании этого материала в первую очередь.

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

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