В чем разница между следующим делегатом Func <Task <T>> async?

Если у меня есть следующий метод:

public async Task<T> DoSomethingAsync<T>(Func<Task<T>> action)
{
   // bunch of async code..then "await action()"
}

В чем разница между следующими двумя обычаями:

public async Task MethodOneAsync()
{
   return await DoSomethingAsync(async () => await SomeActionAsync());
}

public async Task MethodTwoAsync()
{
   return await DoSomethingAsync(() => SomeActionAsync());
}

Оба компилируются.. оба работают.. нет R # предупреждения...

Какая разница (если таковая имеется)? Будут ли оба метода запускать true async, если они ожидаются вызывающим?

Ответ 1

Короткий ответ

MethodOneAsync() действительно Async и должен использоваться, но MethodTwoAsync() не является подлинным Async, поскольку он вызывает поток пула потоков

Длинный ответ

Для тестирования и управления я упростил ваш код следующим образом:

Выполнение из метода Основной Linqpad следующим образом:

var resultTask = MethodOneAsync(); // Comment one the methods

resultTask.Result.Dump();

Фактический код

public async Task<int> DoSomethingAsync(Func<Task<int>> action)
{
    return await Task.FromResult<int>(3);
}

public async Task<int> MethodOneAsync()
{
    await Task.Delay(10);
    return await DoSomethingAsync(async () => await Task.FromResult<int>(3));
}

public async Task<int> MethodOneAsync()
{
    await Task.Delay(10);
    return await DoSomethingAsync(() => Task.FromResult<int>(3));
}

Теперь я рассмотрел IL generated между двумя вызовами, а следующее - самое важное различие:

Первый вызов с Async and Await внутри DoSomethingAsync имеет следующий IL:

<>c.<MethodOneAsync>b__2_0:
IL_0000:  newobj      UserQuery+<>c+<<MethodOneAsync>b__2_0>d..ctor
IL_0005:  stloc.0     
IL_0006:  ldloc.0     
IL_0007:  ldarg.0     
IL_0008:  stfld       UserQuery+<>c+<<MethodOneAsync>b__2_0>d.<>4__this
IL_000D:  ldloc.0     
IL_000E:  call        System.Runtime.CompilerServices.AsyncTaskMethodBuilder<System.Int32>.Create
IL_0013:  stfld       UserQuery+<>c+<<MethodOneAsync>b__2_0>d.<>t__builder
IL_0018:  ldloc.0     
IL_0019:  ldc.i4.m1   
IL_001A:  stfld       UserQuery+<>c+<<MethodOneAsync>b__2_0>d.<>1__state
IL_001F:  ldloc.0     
IL_0020:  ldfld       UserQuery+<>c+<<MethodOneAsync>b__2_0>d.<>t__builder
IL_0025:  stloc.1     
IL_0026:  ldloca.s    01 
IL_0028:  ldloca.s    00 
IL_002A:  call        System.Runtime.CompilerServices.AsyncTaskMethodBuilder<System.Int32>.Start<<<MethodOneAsync>b__2_0>d>
IL_002F:  ldloc.0     
IL_0030:  ldflda      UserQuery+<>c+<<MethodOneAsync>b__2_0>d.<>t__builder
IL_0035:  call        System.Runtime.CompilerServices.AsyncTaskMethodBuilder<System.Int32>.get_Task
IL_003A:  ret      

Второй без Async and Await имеет следующий код:

<>c.<MethodOneAsync>b__2_0:
IL_0000:  ldc.i4.3    
IL_0001:  call        System.Threading.Tasks.Task.FromResult<Int32>
IL_0006:  ret      

Кроме того, у первого есть полный код конечного автомата для дополнительного вызова async await, который ожидается.

Важные моменты:

  • Для вызова метода Async используйте async () => await SomeActionAsync(), так как это правда. Выполнение Async и работает с портами завершения ввода-вывода.
  • В другом случае он вызывает поток Threadpool для выполнения Async-метода, что не подходит для асинхронного выполнения

Я могу вставить полный IL, если потребуется, чтобы понять разницу, но лучше всего вы оцениваете то же самое в Visual Studio или LinqPad, чтобы понять нюансы

Ответ 2

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

Причина, по которой доступен первый подход, заключается в том, что у вас может быть нетривиальное лямбда-выражение, подобное этому:

public async Task MethodOneAsync()
{
    return await DoSomethingAsync(async () => {
        var i = _isItSunday ? 42 : 11;
        var someResult = await SomeActionAsync(i);
        return await AnotherActionAsync(someResult*i);
    });
}

Таким образом, разница такая же, как разница между методом с этой подписью public async Task<int> MyMethod и с этой public Task<int> MyMethod

Ответ 3

Конструкция Async/await вставляет некоторый код инфраструктуры, который полезен только в том случае, если после "ожидания" есть некоторый код. В противном случае он практически ничего не делает. Ваш код эквивалентен

public Task MethodThreeAsync()
{
    return DoSomethingAsync(() => SomeActionAsync());
}

Все три метода: "true async".