Яка різниця між поверненням порожнечі та поверненням завдання?


128

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

Відповіді:


214

Відповіді SLaks та Killercam хороші; Я думав, що просто додам трохи більше контексту.

Ваше перше питання - це, по суті, про те, які методи можна позначити async.

Метод, позначений як asyncможе повернутися void, Taskабо Task<T>. Які відмінності між ними?

Метод Task<T>повернення асинхронізації можна чекати, і коли завдання буде виконано, він відкриє T.

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

Метод voidповернення асинхронізації не можна чекати; це метод "пожежі та забудь". Це працює асинхронно, і у вас немає способу сказати, коли це робиться. Це більш ніж трохи дивно; як каже SLaks, ви зазвичай робите це лише при створенні асинхронного обробника подій. Подія запускається, обробник виконує; ніхто не збирається "чекати" завдання, поверненого обробником події, оскільки обробники подій не повертають завдання, і навіть якщо вони це зробили, який код використовував би завдання для чогось? Зазвичай це не код користувача, який передає керування обробнику в першу чергу.

Ваше друге запитання, в коментарі, по суті стосується того, що можна awaitредагувати:

Які методи можна awaitредагувати? Чи можна змінити метод повернення недійсності await?

Ні, методу повернення недійсності не можна чекати. Компілятор перекладається await M()на виклик M().GetAwaiter(), де GetAwaiterможе бути метод екземпляра або метод розширення. Очікувана цінність повинна бути такою, за яку можна отримати офіціанта; Очевидно, що метод, що повертає недійсність, не дає значення, з якого можна отримати офіціанта.

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

(ОНОВЛЕННЯ: Моє останнє речення там може бути сфальсифіковано майбутньою версією C #; є пропозиція дозволити типи повернення, крім типів завдань, для методів асинхронізації.)

(ОНОВЛЕННЯ: Згадана вище функція перетворила її на C # 7.)


7
+1 Я думаю, що єдине, чого не вистачає, - це різниця в тому, як трапляються винятки в методах асинхронної повернення, що повертаються до недійсності.
Жоао Анджело

10
@JamesCadd: Припустимо, деяка асинхронна робота кидає виняток. Хто це ловить? Код, який розпочав асинхронну задачу, вже не стоїть на стеці - він може бути навіть не в одній нитці - і винятки припускають, що всі блоки захоплення / нарешті знаходяться на стеці . Так, що ти робиш? Ми зберігаємо інформацію про винятки у Завданні, щоб потім можна було її перевірити. Але якщо метод недійсний, тоді код користувача не доступний. Як саме ми розбираємося з цією ситуацією, було предметом певної суперечки, і я не пригадую на даний момент, що ми вирішили.
Ерік Ліпперт

8
Я фактично задав це питання Стівена Туба в BUILD. У .NET 4.0, що не спостерігається, необроблені винятки в Завданнях врешті-решт завершать процес, як тільки TPL виявить, що їх не спостерігали. У 4.5 вони змінили поведінку за замовчуванням, так що про непомічені винятки все ще будуть повідомлятися через події TaskScheduler :: UnobservedTaskException, але вони більше не завершать процес. Якщо ви хочете старої поведінки 4.0, ви можете увімкнути цю функцію за допомогою <runtime> <ThrowUnobservedTaskExceptions enable = "true" /> </runtime>. Швидше за все, зміна була зроблена саме для підтримки методів "забуття та забуття" для недійсних методів асинхронізації.
Дрю Марш

4
async voidметоди підвищують свій виняток із того, SynchronizationContextщо було активним у той час, коли вони почали виконувати. Це схоже на поведінку (синхронних) обробників подій. @DrewMarsh: UnobservedTaskExceptionі виконання установка застосовуються тільки до «вогню і забути» асинхронну Цільові методи, а НЕ async voidметоди.
Стівен Клірі

1
Посилання на посилання на інформацію про обробку винятків async: blogs.msdn.com/b/pfxteam/archive/2012/04/12/10293335.aspx#11
Luke Puplett

23

У випадку, якщо абонент хоче почекати завдання або додати продовження.

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


Я думав, що можна чекати методів, які також повертають тип пустоти - ви могли б трохи розробити?
Джеймс Кейд

1
Ні, ти не можеш. Якщо метод повертається void, у вас немає можливості отримати завдання, яке він створює. (Насправді я не впевнений, чи взагалі це генерує Task)
SLaks

18

Методи, що повертаються Taskта Task<T>є композиційними - це означає, що ви можете використовувати awaitїх всередині asyncметоду.

asyncметоди, що повертаються void, не є композиційними, але вони мають дві інші важливі властивості:

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

Другий момент важливий, коли ви маєте справу з контекстом, який підтримує кількість видатних асинхронних операцій.

Контекст ASP.NET є одним з таких контекстів; якщо ви використовуєте Taskметоди асинхронізації, не чекаючи їх від асинхронного voidметоду, запит ASP.NET буде завершено занадто рано.

Інший контекст - це те, що AsyncContextя написав для тестування одиниць (доступний тут ) - AsyncContext.Runметод відстежує непогашений підрахунок операцій і повертається, коли він дорівнює нулю.


12

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

Точно, як буде створено результат типу, Tта деталізація конкретного завдання; робота може бути передана в інший процес на локальній машині, в інший потік і т. д. Завдання TPL, як правило, розробляються робочими потоками з пулу потоків у поточному процесі, але ця деталізація реалізації не є принциповою для Task<T>типу; скоріше, а Task<T>може представляти будь-яку операцію з високою затримкою, яка виробляє a T.

На підставі Вашого коментаря вище:

У awaitвираз означає «обчислити цей вираз , щоб отримати об'єкт , який представляє роботу , що в майбутньому буде виробляти результат. Зареєструватися залишок поточного методу в якості зворотного виклику , пов'язаного з продовженням цього завдання. Після того, як ця задача проводиться і на зворотний дзвінок підписано, негайно поверніть контроль моєму абоненту ". Це протилежне / на відміну від звичайного виклику методу, який означає "запам'ятайте, що ви робите, запустіть цей метод до повного завершення, а потім виберіть місце, де ви зупинилися, тепер знаючи результат методу".


Редагувати: Я маю навести статтю Еріка Ліпперта в жовтні 2011 р. Журнал MSDN, оскільки це мені було дуже корисно в розумінні цього матеріалу.

Про навантаження більше інформації та побілки дивіться тут .

Я сподіваюся, що це допоможе.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.