下面的函数Func>异步委托方法

本文关键字:异步 方法 Task 函数 Func | 更新日期: 2023-09-27 18:12:57

如果我有以下方法:

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());
}

都可以编译,都可以工作,并且没有c#警告。

有什么区别(如果有的话)?如果调用者等待,这两个方法都将真正异步运行吗?

下面的函数Func<Task<T>>异步委托方法

两者在功能上没有区别。唯一的区别是,如果直接返回来自SomeActionAsync的Task,还是等待它。Stephen Cleary对此有一篇很好的博客文章,并推荐了第二种方法。

第一种方法可用的原因是您可以有一个像这样的非平凡lambda表达式:

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的方法的区别一样

简短回答

MethodOneAsync()是真正的异步,应该使用,但MethodTwoAsync()不是真正的异步,因为它调用线程池线程

长回答

为了测试和运行的目的,我简化了你的代码如下:

从Linqpad的Main方法执行如下:

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,以下是最重要的区别:

DoSomethingAsync内部第一次调用Async and Await有以下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调用的完整状态机代码,这是预期的。

重要的几点:

  1. 对于异步方法调用使用async () => await SomeActionAsync(),因为这是真正的异步执行和工作在IO完成端口
  2. 在其他情况下,它调用Threadpool线程来执行异步方法,这对于异步执行来说不是很好

如果需要,我可以粘贴完整的IL,以了解差异,但最好是您在Visual studio或LinqPad中评估相同的内容,以了解细微差别

首先,您的示例没有意义,您要么返回一些东西,要么没有,返回等待函数的结果而没有返回类型将是编译器错误。

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

其次,这与"true"async无关,因为这将是一个没有显示的实现细节

第三,两个假设的例子之间的唯一区别

await DoSomethingAsync(async () => await SomeActionAsync());

await DoSomethingAsync(() =>  SomeActionAsync()); 

给定DoSomethingAsync的定义,在第一个示例中,编译器将创建一个额外的IAsyncStateMachine 实现,而不是仅仅转发Task。也就是说,更多的编译代码,更多的IL,更多的指令,在这个例子中似乎是多余的。

省略任务时,异常有一个小警告,但是因为这只是一个简单的通过,没有其他代码会抛出,因此额外的状态机或try catchTask.FromException是不需要的。

如果您的签名实际上是Action而不是Func<Task>,这将创建一个给定async lambdaasync void,那么真正明显的差异将会出现,但在您的问题中并非如此。

Async/await构造插入一些基础结构代码,只有在"await"之后有一些代码时才有用。否则它什么都不做。你的代码相当于

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

这三个方法都是"true async"