await await vs Unwrap()

hat*_*cyl 23 c# async-await

给出一种方法如

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)


Ser*_*rvy 8

没有任何功能差异.

  • @hatcyl您需要什么参考?`await`打开一个任务。这就是“做什么” *。根据定义,它们相等。 (2认同)

cod*_*ave 5

我运行上面未经修改的示例,得到了不同的结果(两者都是正确的,因此它们是等效的。我在 .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)

我的测量结果与接受的答案有很大不同:

  • async-async 和 async-Unwrap 之间的速度差异非常小,我们谈论的是纳秒。
  • async-async 比 async-Unwrap 具有内存优势,后者也非常小。

就框架而言,.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 执行速度更快,内存效率也更高。)

  • 请注意,作为优化,如果外部任务在您调用“Unwrap”时已运行完成,“Unwrap”将仅返回对内部任务的引用*。如果外部任务尚未完成,它不可能返回引用相等的任务(因为该任务可能尚不存在)。当然,您确实不应该编写关心这样的任务引用的代码,只是两个任务具有相同的结果并且在(大约)同时完成。 (3认同)