Как стать автором
Обновить

Комментарии 10

Меня терзают сомнения на счет полезности dynamic.

Для вдохновления могу посоветовать почитать:

Processing Sequences of Asynchronous Operations with Task
http://blogs.msdn.com/b/pfxteam/archive/2010/11/21/10094564.aspx?Redirected=true

The task monad in C#
http://ruudvanasseldonk.com/2013/05/01/the-task-monad-in-csharp
Dapper?
А зачем вообще сложности типа использования async/await когда результатом функции является задача?
Зачем передавать в создание таски async () => await inner.
Это же и есть причина ошибки.

Например чтобы не получить TaskCancelledException в основном потоке при простой попытке остановить запущенную задачу.

> есть ли в данном примере проблема?

Да, есть проблема, но, как и у g_DiGGeR, проблема в понимании: зачем в Compute передавать Task; a если уж приспичило передавать именно Task, то зачем его там стартовать вместо того, чтобы сразу отдать (безо всякого return await); а если вдруг нужно дождаться результата inner и использовать внутри Compute, то почему не использовать await inner напрямую, без StartNew с асинхронной блямбдой и т.п.?

> Всегда используйте метод-расширение Unwrap для Task.Factory.StartNew()

Вызывать Unwrap после StartNew нужно лишь в случае когда передаётся асинхронный делегат в качестве параметра (как в вашем примере), потому что такой делегат вернёт Task, который завернётся в ещё один Task, и его нужно развернуть. В прочих случаях никакой Unwrap не нужен.
См. http://blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx

А dynamic тут вообще не при чём — Unwrap нужен с любым типом результата. Task.Run уже содержит внутри Unwrap() (там, где это нужно), так что да, Run — это не просто StartNew с дефолтными параметрами.

Вместо Unwrap можно использовать двойной await (return await await ...)
Вызывать Unwrap после StartNew нужно лишь в случае когда передаётся асинхронный делегат в качестве параметра (как в вашем примере), потому что такой делегат вернёт Task, который завернётся в ещё один Task, и его нужно развернуть. В прочих случаях никакой Unwrap не нужен.

Естественно. об этом и речь. Так в статье я писал, что все дело в перегрузках:
public static Task<TResult> Run<TResult>(Func<Task<TResult>> function)
public Task<TResult> StartNew<TResult>(Func<TResult> function);

Несмотря на то, что возвращаемый тип у обоих методов — Task, входным параметром у Run является Func<Task<TResult>>.
Кстати у Вас идет противоречие:
В прочих случаях никакой Unwrap не нужен
+
Unwrap нужен с любым типом результата
+
Вместо Unwrap можно использовать двойной await (return await await ...)

т.е. Вы статью не читали, так? ибо особенно показательно я призываю не использовать просто так два await'a подряд:
Всегда используйте метод-расширение Unwrap для Task.Factory.StartNew(). Это сделает ваш код более идиоматичным (один await на вызов) и не допустит хитростей dynamic.
Чтобы стало нагляднее, рассмотрим пример:
static Task<T> Compute<T>(Task<T> inner)
{
return Task.Run(async () => await inner);
}

static async Task<T> ComputeWithFactory<T>(Task<T> inner)
{
return await await Task.Factory.StartNew(async () => await inner);
}


И, естественно, если читатель понял контекст всей статьи, что мы рассматриваем именно не-void случаи, тогда таких вопросов не должно возникать.
Кстати, если будет выбрана перегрузка с простым Action, а внутри окажется таск, то, соответсвенно, это уже будет асинхронная лямбда с возвращаемым типом void.
Кажется, в каждом туториале пишут большими буквами: никогда не используйте async void.
Мне это пересказывать? зачем? Если читатель не знаком с данным постулатом, то и остальное не пригодится.
И последнее:
Да, есть проблема, но, как и у g_DiGGeR, проблема в понимании: зачем в Compute передавать Task; a если уж приспичило передавать именно Task, то зачем его там стартовать вместо того, чтобы сразу отдать (безо всякого return await); а если вдруг нужно дождаться результата inner и использовать внутри Compute, то почему не использовать await inner напрямую, без StartNew с асинхронной блямбдой и т.п.?

In real life так просто не будет. Рефакторинги происходят, код пишется разными людьми. Мира с единорогами нет, где, допустим, в ComputeAsync попадет что-простое. А уж таких ComputeAsync с проблемами было n-ое кол-во.
Моя мысль простая: один await на async метод. Если возвращаемый тип Task<Task<T>>, тогда Unwrap() нужен, ибо для Task<T> бесполезно.
Типичный пример, когда нужно: https://blogs.msdn.microsoft.com/pfxteam/2011/01/15/asynclazyt/

That could be perfectly reasonable if the taskFactory delegate does very little work before returning the task instance. If, however, the taskFactory delegate does any non-negligable work, a call to Value would block until the call to taskFactory completes. To cover that case, the second approach is to run the taskFactory using Task.Factory.StartNew, i.e. to run the delegate itself asynchronously, just as with the first constructor, even though this delegate already returns a Task. Of course, now StartNew will be returning a Task<Task>, so we use the Unwrap method in .NET 4 to convert the Task<Task> into a Task, and that’s what’s passed down the base type.
> Кстати у Вас идет противоречие:
> В прочих случаях никакой Unwrap не нужен
> +
> Unwrap нужен с любым типом результата

Под «любым типом результата» подразумевался не результат функции, а TResult — хоть dynamic, хоть int. Так что тут нет противоречия.

С двойным await каюсь, был невнимателен.

> если будет выбрана перегрузка с простым Action, а внутри окажется таск, то, соответсвенно, это уже будет асинхронная лямбда с возвращаемым типом void.

Не совсем понял, внутри чего окажется таск? И я что-то в три часа ночи уже не соображу, как совместить перегрузку по Action (который и сам синхронный и не возвращает ничего асинхронного) с асинхронной лямбдой? Ну если только не ставить async-и и лямбды бездумно куда ни попадя.
Не совсем понял, внутри чего окажется таск? И я что-то в три часа ночи уже не соображу, как совместить перегрузку по Action (который и сам синхронный и не возвращает ничего асинхронного) с асинхронной лямбдой? Ну если только не ставить async-и и лямбды бездумно куда ни попадя.

Я имел ввиду момент, когда даже если передается Action, то последний может быть помечен async модификатором как из-за await'a внутри тела определенного таска, так и по ошибке.
Например:
static void ComputeNoReturn(Action inner)
{
    inner();
}

ComputeNoReturn(async () =>
{
    throw new Exception();
});
ComputeNoReturn(async () =>
{
    await Task.Run(() => {});
});

Так что, просто Action и async Action — две разные вещи, т.к. обрабатываются компилятором совершенно по-разному. Ну и в тему.

Удивляет, зачем использовать динамик (сознательно) вместо нормальной параметризации и жаловаться на это? Как только типы проставлены, компилятор заставит подумать.


Решение использвать Unwrap это не решение, а просто корректный способ использования TPL.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации