给出一种方法如
public async Task<Task> ActionAsync()
{
...
}
Run Code Online (Sandbox Code Playgroud)
有什么区别
await await ActionAsync();
Run Code Online (Sandbox Code Playgroud)
和
await ActionAsync().Unwrap();
Run Code Online (Sandbox Code Playgroud)
如果有的话.
Leo*_*lev 40
Unwrap()创建一个新的任务实例,表示每次调用的整个操作.与以await这种方式创建的任务相比,与原始内部任务不同.请参阅Unwrap()文档,并考虑以下代码:
private async static Task Foo()
{
Task<Task<int>> barMarker = Bar();
Task<int> awaitedMarker = await barMarker;
Task<int> unwrappedMarker = barMarker.Unwrap();
Console.WriteLine(Object.ReferenceEquals(originalMarker, awaitedMarker));
Console.WriteLine(Object.ReferenceEquals(originalMarker, unwrappedMarker));
}
private static Task<int> originalMarker;
private static Task<Task<int>> Bar()
{
originalMarker = Task.Run(() => 1);;
return originalMarker.ContinueWith((m) => m);
}
Run Code Online (Sandbox Code Playgroud)
输出是:
True
False
Run Code Online (Sandbox Code Playgroud)
使用.NET 4.5.1的基准测试进行更新:我测试了两个版本,结果发现带有double的版本await在内存使用方面更好.我使用Visual Studio 2013内存分析器.测试包括每个版本的100000个调用.
64位:
??????????????????????????????????????????????????????????????
? Version ? Inclusive Allocations ? Inclusive Bytes ?
??????????????????????????????????????????????????????????????
? await await ? 761 ? 30568 ?
? await + Unwrap() ? 100633 ? 8025408 ?
??????????????????????????????????????????????????????????????
Run Code Online (Sandbox Code Playgroud)
86:
??????????????????????????????????????????????????????????????
? Version ? Inclusive Allocations ? Inclusive Bytes ?
??????????????????????????????????????????????????????????????
? await await ? 683 ? 16943 ?
? await + Unwrap() ? 100481 ? 4809732 ?
??????????????????????????????????????????????????????????????
Run Code Online (Sandbox Code Playgroud)
我运行上面未经修改的示例,得到了不同的结果(两者都是正确的,因此它们是等效的。我在 .NET SDK 6.0.300 上进行了测试,但它应该适用于所有情况)。
然后我稍微改进了代码以使用推荐的异步等待最佳实践并验证了我的发现:
public static class Program
{
public static async Task Main()
{
await Run().ConfigureAwait(false);
}
public async static Task Run()
{
Task<Task<int>> barMarker = GetTaskOfTask();
Task<int> awaitedMarker = await barMarker.ConfigureAwait(false);
Task<int> unwrappedMarker = barMarker.Unwrap();
Out(ReferenceEquals(_originalMarker, awaitedMarker));
Out(ReferenceEquals(_originalMarker, unwrappedMarker));
}
private static Task<int> _originalMarker = Task.Run(() => 1);
private static Task<Task<int>> GetTaskOfTask()
{
return _originalMarker.ContinueWith((m) => m, TaskScheduler.Default);
}
private static void Out(object t)
{
Console.WriteLine(t);
Debug.WriteLine(t);
}
}
Run Code Online (Sandbox Code Playgroud)
输出是:
True
True
Run Code Online (Sandbox Code Playgroud)
然后我对代码进行了基准测试:
BenchmarkDotNet=v0.13.1, OS=Windows 10.0.19044.1706 (21H2)
AMD Ryzen 7 3700X, 1 CPU, 16 logical and 8 physical cores
.NET SDK=6.0.300
[Host] : .NET 6.0.5 (6.0.522.21309), X64 RyuJIT
.NET 5.0 : .NET 5.0.17 (5.0.1722.21314), X64 RyuJIT
.NET 6.0 : .NET 6.0.5 (6.0.522.21309), X64 RyuJIT
.NET Core 3.0 : .NET Core 3.1.25 (CoreCLR 4.700.22.21202, CoreFX 4.700.22.21303), X64 RyuJIT
.NET Framework 4.6.1 : .NET Framework 4.8 (4.8.4510.0), X64 RyuJIT
.NET Framework 4.7.2 : .NET Framework 4.8 (4.8.4510.0), X64 RyuJIT
.NET Framework 4.8 : .NET Framework 4.8 (4.8.4510.0), X64 RyuJIT
CoreRT 3.0 : .NET 6.0.0-rc.1.21420.1, X64 AOT
```
| Method | Job | Runtime | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Allocated |
|----------- |--------------------- |--------------------- |-----------:|---------:|----------:|------:|--------:|-------:|-------:|----------:|
|AsynsUnwrap | .NET 5.0 | .NET 5.0 | 1,462.5 ns | 5.07 ns | 4.74 ns | 1.02 | 0.01 | 0.0458 | - | 386 B |
|AsynsUnwrap | .NET 6.0 | .NET 6.0 | 1,435.2 ns | 6.71 ns | 6.27 ns | 1.00 | 0.00 | 0.0458 | - | 385 B |
|AsynsUnwrap | .NET Core 3.0 | .NET Core 3.0 | 1,539.0 ns | 2.09 ns | 1.96 ns | 1.07 | 0.00 | 0.0458 | - | 386 B |
|AsynsUnwrap | .NET Framework 4.6.1 | .NET Framework 4.6.1 | 2,286.3 ns | 5.33 ns | 4.98 ns | 1.59 | 0.01 | 0.0839 | 0.0038 | 546 B |
|AsynsUnwrap | .NET Framework 4.7.2 | .NET Framework 4.7.2 | 2,267.3 ns | 6.66 ns | 5.90 ns | 1.58 | 0.01 | 0.0839 | 0.0038 | 546 B |
|AsynsUnwrap | .NET Framework 4.8 | .NET Framework 4.8 | 2,307.6 ns | 9.04 ns | 8.45 ns | 1.61 | 0.01 | 0.0839 | 0.0038 | 546 B |
|AsynsUnwrap | CoreRT 3.0 | CoreRT 3.0 | 413.2 ns | 3.78 ns | 3.54 ns | 0.29 | 0.00 | 0.0467 | - | 391 B |
| | | | | | | | | | | |
| AsyncAsync | .NET 5.0 | .NET 5.0 | 1,496.7 ns | 1.20 ns | 1.00 ns | 1.08 | 0.01 | 0.0381 | - | 332 B |
| AsyncAsync | .NET 6.0 | .NET 6.0 | 1,391.5 ns | 8.25 ns | 7.72 ns | 1.00 | 0.00 | 0.0381 | - | 332 B |
| AsyncAsync | .NET Core 3.0 | .NET Core 3.0 | 1,508.5 ns | 36.04 ns | 104.55 ns | 1.07 | 0.11 | 0.0381 | - | 332 B |
| AsyncAsync | .NET Framework 4.6.1 | .NET Framework 4.6.1 | 2,179.8 ns | 11.64 ns | 10.89 ns | 1.57 | 0.01 | 0.0725 | 0.0038 | 483 B |
| AsyncAsync | .NET Framework 4.7.2 | .NET Framework 4.7.2 | 2,213.6 ns | 8.31 ns | 7.37 ns | 1.59 | 0.01 | 0.0725 | 0.0038 | 483 B |
| AsyncAsync | .NET Framework 4.8 | .NET Framework 4.8 | 2,195.1 ns | 9.87 ns | 9.23 ns | 1.58 | 0.01 | 0.0725 | 0.0038 | 483 B |
| AsyncAsync | CoreRT 3.0 | CoreRT 3.0 | 380.3 ns | 4.55 ns | 4.03 ns | 0.27 | 0.00 | 0.0401 | - | 336 B |
Run Code Online (Sandbox Code Playgroud)
我的测量结果与接受的答案有很大不同:
就框架而言,.Net Framework 的执行速度比 .Net Core 3/.Net 5/.Net 6 的代码慢约 60%,消耗的内存多约 30%。它也出现在 Gen1 中,因此垃圾收集器在整个框架上承受的压力更高。不幸的是,BenchmarkDotNet 最高支持 .NET Framework 4.6.1,因此我无法将我的发现与 .Net Framework 4.5.1 进行比较。
结论:如果您希望最大限度地减少内存占用,因为这对您的情况至关重要,您可能需要使用await。在任何其他情况下,async-Unwrap 都会获胜,因为代码更明确并且更易于阅读。(较新版本的 .NET 执行速度更快,内存效率也更高。)