Ожидание или Задача. FromResult

У меня есть одна служба, скажем,

public interface ISomeService
{
    Task<bool> DoSomeExpensiveCheckAsync(string parameter);
}

И у меня есть этот класс, чтобы использовать службу. Он просто должен выполнить несколько простых нулевых проверок, а затем вернуть ответ службы.

public class SomeServiceConsumer
{
    private readonly ISomeService _serviceClient;

    public SomeServiceConsumer(ISomeService serviceClient)
    {
        _serviceClient = serviceClient;
    }

    public async Task<bool> DoSomething1Async(string someParameter)
    {
        if (string.IsNullOrWhiteSpace(someParameter))
        {
            return false;
        }
        return await _serviceClient.DoSomeExpensiveCheckAsync(someParameter);
    }

    //No async or await keywords   
    public Task<bool> DoSomething2Async(string someParameter)
    {
        if (string.IsNullOrWhiteSpace(someParameter))
        {
            return Task.FromResult(false);
        }
        return _serviceClient.DoSomeExpensiveCheckAsync(someParameter);
    }
}

Должен ли я делать DoSomething1Async или DoSomething2Async?

В соответствии с этим ответом я не должен обертывать ненужным await, но тогда я должен использовать Task.FromResult(false) для короткого вызова, как в DoSomething2Async

Но в соответствии с этим ответом есть случаи с операторами try/catch и using, где я должен был await перед возвратом.

Правильно ли я говорю, что

  • Если мне нужно использовать try/catch или using, тогда я должен await

  • В противном случае не await, если вы только вернетесь. И используйте Task.FromResult для короткого замыкания

Мне нравится DoSomething1Async больше, и я хочу сделать это везде, если кто-то скажет, что это не имеет значения:).

Ответ 1

Если вы беспокоитесь об этом, кешируйте Task:

static readonly Task<bool> falseTask = Task.FromResult(false);

Ключевое слово async также включает исключения в возвращенном Task, а также правильную трассировку стека. Это компромисс, безопасность поведения для перформанса.

Давайте рассмотрим разностные сценарии, в которых каждый будет отличаться:

async Task UseSomething1Async(string someParameter)
{
    // if IsNullOrWhiteSpace throws an exception, it will be wrapped in
    // the task and not thrown here.
    Task t1 = DoSomething1Async(someParameter);

    // rather, it'll get thrown here. this is best practice,
    // it what users of Task-returning methods expect.
    await t1;

    // if IsNullOrWhiteSpace throws an exception, it will
    // be thrown here. users will not expect this.
    Task t2 = DoSomething2Async(someParameter);

    // this would never have been reached.
    await t2;
}

Просто проиллюстрируем здесь пункт - IsNullOrWhiteSpace на самом деле не бросает никаких исключений по какой-либо причине.

Что касается трассировки стека, то трассировка стека асинхронов определяется тем, где вы await. Нет await означает, что метод исчезнет из трассировки стека.

Скажите DoSomeExpensiveCheckAsync выдает исключение. В случае DoSomething1Async трассировка стека будет выглядеть как caller -> DoSomething1Async -> DoSomeExpensiveCheckAsync.

В случае DoSomething2Async трассировка стека будет выглядеть как caller -> DoSomeExpensiveCheckAsync. В зависимости от сложности вашего кода это может затруднить отладку.

На практике я обычно буду только возвращать Task, если бы я знал, что перед ним не будут исключены исключения, и если имя метода было просто перегрузкой, пересылающей другую перегрузку. Всегда есть исключения из этого правила, обязательно должны быть места, которые вы хотите максимизировать производительность. Просто выберите и выберите тщательно, поймите, что вы можете сделать жизнь вас и вашего пользователя более трудной.

Ответ 2

На самом деле это не имеет значения.. Если вам удобнее всегда маркировать Task -приведение методов с помощью ключевого слова async, то используйте DoSomething1.

Как вы сказали, это компромисс:

  • DoSomething2 не создает конечный автомат, необходимый для метода async, и поэтому он немного быстрее (но разница в основном незначительна).

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