Task<T>
аккуратно держит "начатое, может быть закончено" вычисление, которое может быть составлено с другими задачами, сопоставлено с функциями и т.д. Напротив, F # async
monad проводит вычисление "может начаться позже, возможно, теперь выполняется" вместе с CancellationToken
. В С# вам обычно нужно прокручивать CancellationToken
через каждую функцию, которая работает с Task
. Почему команда С# решила обернуть вычисление в монаде Task
, но не CancellationToken
?
Почему в "Задача" <T> монада не входит аннулирование?
Ответ 1
Более или менее они инкапсулировали неявное использование методов CancellationToken
для С# async
. Рассмотрим это:
var cts = new CancellationTokenSource();
cts.Cancel();
var token = cts.token;
var task1 = new Task(() => token.ThrowIfCancellationRequested());
task1.Start();
task1.Wait(); // task in Faulted state
var task2 = new Task(() => token.ThrowIfCancellationRequested(), token);
task2.Start();
task2.Wait(); // task in Cancelled state
var task3 = (new Func<Task>(async() => token.ThrowIfCancellationRequested()))();
task3.Wait(); // task in Cancelled state
Для неасинхронной лямбда мне пришлось явно связать token
с task2
, чтобы аннулирование распространялось правильно, предоставив его как аргумент new Task()
(или Task.Run
). Для async
лямбда, используемого с task3
, это происходит автоматически как часть кода инфраструктуры async/await
.
Кроме того, любой token
будет распространять отмену для метода async
, тогда как для неасинхронного вычислительного new Task()
/Task.Run
lambda он должен быть тем же самым токеном, переданным конструктору задачи или Task.Run
.
Конечно, нам все равно придется называть token.ThrowIfCancellationRequested()
вручную, чтобы реализовать шаблон аннулирования сотрудничества. Я не могу ответить, почему команды С# и TPL решили реализовать его таким образом, но я думаю, они стремились не чрезмерно усложнять синтаксис async/await
, но сохраняя его достаточно гибким.
Что касается F #, я не посмотрел на сгенерированный код IL асинхронного рабочего процесса, описанный в статье Tomas Petricek в блоге, которую вы связали. Тем не менее, насколько я понимаю, токен автоматически проверяется только в определенных местах рабочего процесса, соответствующих await
в С# (по аналогии, мы могли бы звонить token.ThrowIfCancellationRequested()
вручную после каждого await
в С#). Это означает, что любая работа с процессором по-прежнему не будет отменена немедленно. В противном случае F # должно было бы испускать token.ThrowIfCancellationRequested()
после каждой команды IL, что было бы существенным издержком.
Ответ 2
Сначала была выполнена задача содержать дополнительные функции в классе, но позже была изменена для агрегирования объекта с дополнительной поддержкой функций. Все во имя производительности.
http://blogs.msdn.com/b/pfxteam/archive/2011/11/10/10235962.aspx (см. "Задача реструктуризации" в статье). Статья Джозефа Хоага дает хорошее представление об оптимизации, сделанной в .NET 4.5. Я считаю, что было бы полезно прочитать, кто пытается сжать последние 10% производительности из async/wait.
Я предполагаю, что подобный процесс мышления был применен при принятии решения о том, как упаковать функции отмены.
Я не могу говорить за команду С# или BCL, но я предполагаю, что это оптимизация производительности, которая возможна только в компиляторе F # или производительности был несуществен для команды F #. SRP, baby!