Task.FromResult() vs. Task.Run()

В последнее время я столкнулся с довольно многими ситуациями, когда async методы выполняются синхронно, но в любом случае возвращают задачу, поэтому их можно ожидать, например

public virtual Task CreateAsync(TUser user)
{
    ThrowIfDisposed();
    if (user == null) throw new ArgumentNullException("user");
    Context.Save(user);
    Context.Flush();
    return Task.FromResult(0);
}

Разумеется, лучше отправить вероятную долговременную операцию в поток и вернуть все еще активную задачу, чтобы ее можно было ожидать:

public virtual Task CreateAsync(TUser user)
{
    ThrowIfDisposed();
    if (user == null) throw new ArgumentNullException("user");
    return Task.Run(() =>
    {
        Context.Save(user);
        Context.Flush();
    });
}

Однако я подозреваю, что просто отключение потоков TPL - это не самая безопасная практика. Любой комментарий к этим двум различным шаблонам?

Ответ 1

Если ваш метод синхронный, вы не должны возвращать Task для начала. Просто создайте традиционный синхронный метод.

Если по какой-то причине это невозможно (например, вы реализуете некоторый асинхронный интерфейс), возвращающий завершенную задачу с помощью Task.FromResult или даже лучше в этом случае Task.CompletedTask (добавленный в.NET 4.6) намного лучше, чем использование Task.Run in реализация:

public virtual Task CreateAsync(TUser user)
{
    // ...
    return Task.CompletedTask;
}

Если потребитель вашего API сильно обеспокоен тем, что метод Task -returning не работает синхронно, он может использовать Task.Run чтобы убедиться.

Вы должны иметь в виду, что асинхронные методы могут иметь значительную синхронную часть (часть до первого ожидания), даже если они в конечном итоге продолжаются асинхронно. Вы не можете предположить, что методы async возвращают Task немедленно.

Ответ 2

Task.FromResult фактически не создает или не запускает задачу, а просто переносит возвращаемый результат в объект задачи. Я лично использовал его в Unit Tests где мне нужно моделировать методы Async и, конечно, я бы не захотел запускать реальные задачи в модульных тестах.

Кроме того, Task.Run фактически создаст задачу и запустит задачу TaskScheduler. Не рекомендуется использовать Task.Run когда вы выполняете Async программирование. Скорее используйте await задач. Смотрите, что некоторые делают и не выполняют задачи Стивена Клири.