下面的函数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#警告。
有什么区别(如果有的话)?如果调用者等待,这两个方法都将真正异步运行吗?
两者在功能上没有区别。唯一的区别是,如果直接返回来自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
调用的完整状态机代码,这是预期的。
重要的几点:
- 对于异步方法调用使用
async () => await SomeActionAsync()
,因为这是真正的异步执行和工作在IO完成端口 - 在其他情况下,它调用
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 catch和Task.FromException
是不需要的。
如果您的签名实际上是Action
而不是Func<Task>
,这将创建一个给定async lambda的async void
,那么真正明显的差异将会出现,但在您的问题中并非如此。
Async/await构造插入一些基础结构代码,只有在"await"之后有一些代码时才有用。否则它什么都不做。你的代码相当于
public Task MethodThreeAsync()
{
return DoSomethingAsync(() => SomeActionAsync());
}
这三个方法都是"true async"