在 async/await C# 中苦苦挣扎

本文关键字:挣扎 await async | 更新日期: 2023-09-27 18:34:52

我读了很多关于async/await,但我仍然对以下情况有所了解。

我的

问题是,我应该像DoSomething()一样实现我的"包装器"方法,还是像在DoSomethingAsync()中那样实现我的"包装器"方法。

那么更好的(以及为什么(:我是用包装方法使用await还是直接返回任务?

        public static async void Main()
        {
            await DoSomething();
            await DoSomethingAsync();
        }
        private static Task DoSomething()
        {
            return MyLibrary.DoSomethingAsync();
        }
        private static async Task DoSomethingAsync()
        {
            await MyLibrary.DoSomethingAsync().ConfigureAwait(false);
        }
        public class MyLibrary
        {
            public static async Task DoSomethingAsync()
            {
                // Here is some I/O
            }
        }

在 async/await C# 中苦苦挣扎

Async/Await 仍然相对较新,但有一些很好的做法可以帮助澄清 API。 基础知识是这样的:

  • 将自身声明为 async 的方法意味着它希望稍后await
  • async隐式地为您创建一个Task
  • await就像一个书签。 应用程序在使用 await 关键字的位置恢复。
  • 您不能await任何不IAwaitable的东西(最常见的是Task((引用(

在同时存在异步调用和同步调用的应用程序中,我们采用了命名约定:

  • async调用返回TaskTask<T>,并将单词Async附加到名称的末尾。
  • 同步调用(默认(就像任何方法一样工作,没有特殊的约定。

通常,可以有两种方法执行相同的操作,但一种是同步的,另一种不是。 您可以以两种不同的方式实现它,也可以将一种方式与另一种方式包装在一起。 这实际上取决于您的需求以及将为您提供响应速度更快的应用程序。

在上面的示例中,处理异步和正常方法调用的正确方法是让MyLibrary公开两个方法。 示例如下所示:

 public static class MyLibrary
 {
     public static void DoSomething()
     {
         // do some work
     }
     public static async Task DoSomethingAsync()
     {
         // do similar work but use async API,
         // can also simply call DoSomething().
     }
 }
 // In another part of code would be called like this:
 public static async Task BiggerMethod()
 {
     MyLibrary.DoSomething();
     await MyLibrary.DoSomethingAsync();
 }

您要避免的是用常规方法包装async方法。 一旦直接使用 Task,您将失去asyncawait的所有好处,并且会引入代码可能死锁的地方。

Berin的回答很好,但没有明确解决您问题中的特定情况,即以下示例:

1:直接返回任务

private static Task DoSomething()
{
    return MyLibrary.DoSomethingAsync();
}

2:等待任务并返回结果

private static async Task DoSomethingAsync()
{
    await MyLibrary.DoSomethingAsync().ConfigureAwait(false);
}

在这种情况下,直接返回Task和等待Task并返回响应之间的唯一区别是 - 在后一种情况下 - 框架必须创建一个状态机来管理等待Task的方法的启动、挂起和继续。这会产生一些性能开销。

一般来说,如果你能只返回一个Task并允许它在更高的位置等待,你应该这样做。然而,在大多数(但肯定不是全部(真实情况下,这是不可能的,因为您需要在返回之前对结果执行一些处理(这正是 async/await 帮助您实现的目标(。

单行代码的情况没有区别。但在at least two-async-liners的情况下,例如:

public static async void Main()
{
    await DoSomething();
    await DoSomethingAsync();
}
private static Task DoSomethingTwice()
{
    var do1 = MyLibrary.DoSomethingAsync();
    // when you await DoSomethingTwice, next line's reached
    // when do1 task may not be completed
    var do2 = MyLibrary.DoSomethingAsync();
    // return what???
    // how to combine them?
    // okay, you can do that
    return Task.WhenAll(do1, do2)
}
private static async Task DoSomethingTwiceAsync()
{
    await MyLibrary.DoSomethingAsync().ConfigureAwait(false);
    // when you await DoSomethingTwiceAsync, next line's reached 
    // when prev-line task is completed
    await MyLibrary.DoSomethingAsync().ConfigureAwait(false);
}
public class MyLibrary
{
    public static async Task DoSomethingAsync()
    {
        // Here is some I/O
    }
}

总结:

  • 如果你必须将你的方法组织成一个连续的异步工作和平链:doAsync1() -> doAsync2() -> doAsync3()

即每个下一个和平都需要前一个和平的完整结果,那么您应该等待每个调用:

async Task DoAsync()
{
    await doAsync1();
    await doAsync2();
    await doAsync3();
}
  • 但不能在non-async方法中使用 await。所以你不能以你的Task DoSomething()风格来做,只能按照async Task DoSomethingAsync().
  • 如果没有异步链 - 你可以做任何你想做的事情,就像我上面的第一个例子一样