异步方法中未使用await关键字的awaitable对象是否仍然是异步的?

7

考虑以下代码:

public static void Run() {
    DoStuffAsync();
}

public static async Task DoStuffAsync() {
     PerformCalc();
     await DoLongTaskAsync();
     PerformAnotherCalc();
}

假设我调用了 Run()。关于行为,我有几个问题:

  1. PerformCalc() 方法会同步在与调用 Run() 相同的线程上被调用吗?
  2. DoLongTaskAsync() 方法会异步或同步地被调用?换句话说,DoLongTaskAsync() 完成之前,PerformAnotherCalc() 方法是否可以被调用?
  3. 随后,DoStuffAsync() 方法是否可以在执行 DoLongAsyncTask() 之前返回?

1
  1. 是的。
  2. 不。
  3. 它可以(或者只要DoLongTaskAsync确实是异步的并返回一个未完成的任务)返回,但不会完成
- mm8
1
你可以编写一个小的测试应用程序并自行查看。在不使用await调用带有async签名的方法时,与普通的C#方法一样。 - CodingYoshi
4个回答

11

异步方法始终是同步运行的。魔法发生在await,但仅当给await一个未完成的Task时。

在您的示例中,调用Run()时会发生以下情况:

  1. 跳转到DoStuffAsync()
  2. 跳转到PerformCalc()
  3. 跳转到DoLongTaskAsync()
  4. 如果DoLongTaskAsync()是真正的异步操作并返回一个未完成的Task,则await会执行其工作,并将未完成的Task返回给Run()DoStuffAsync()
  5. 由于任务没有被等待,因此Run()会完成。
  6. DoLongTaskAsync()完成后,DoStuffAsync()恢复,然后跳转到PerformAnotherCalc()

所有这一切可能发生在同一个线程上。

所以回答你的问题:

  1. 是的。如果它是一个async方法,它可能会在其他线程上执行某些操作。但它会在同一个线程上同步开始。
  2. DoLongTaskAsync()将异步调用,但在DoLongTaskAsync()完成之前,PerformAnotherCalc()不会被调用,因为您使用了await
  • 是的。这就是await的工作原理。它将返回一个不完整的Task (即,仅当DoLongTaskAsync()确实是异步的并返回一个不完整的Task时)。然后,一旦DoLongTaskAsync()完成,DoStuffAsync()的执行将从离开的地方继续。

  • 2
    好的回答。一个小提示:异步方法始终相对于调用方同步开始运行。这很重要,因为如果调用方已经在异步运行,它将对调用方同步,但不一定对调用方的调用方同步。我相信这就是你的意思,但可能有助于避免混淆。 - CodingYoshi

    3
    1. PerformCalc()方法会在调用Run()的线程上同步调用吗?

    是的。

    1. DoLongTaskAsync()方法会异步或同步调用?换句话说,在DoLongTaskAsync()完成之前,PerformAnotherCalc()方法会被调用吗?

    它将同步调用,但它可能会在“长时间任务”操作完成之前返回一个Task。无论哪种方式,它返回的Task都会被等待,因此只有在从DoLongTaskAsync返回的Task完成后,PerformAnotherCalc才会被调用。

    1. 随后,DoStuffAsync()方法可以在DoLongAsyncTask()执行完成之前返回吗?

    如果正在等待的Task尚未完成,则DoStuffAsync方法会在第一个await处返回(当然仅当Task处于挂起状态时)。这就是async方法的工作原理-它们在同步运行到第一个Taskawait之前,并返回一个Task,该Task将在整个方法执行完成后完成。

    如果DoLongTaskAsync返回的Task已经完成,那么DoStuffAsync将一直等待,直到PerformAnotherCalc返回。如果DoLongTaskAsync返回的Task仍处于挂起状态,则DoStuffAsync将在此时返回,并返回一个Task,该Task将在从DoLongTaskAsync返回的Task完成后完成,并且PerformAnotherCalc已经返回。


    1

    换句话说,如果您考虑这些方法:

    public static void Run1() {
        DoStuffAsync();
        Console.WriteLine("Done");
    }
    
    public static async Task Run2() {
        await DoStuffAsync();
        Console.WriteLine("Done");
    }
    
    public static async Task DoStuffAsync() {
         PerformCalc();
         await DoLongTaskAsync();
         PerformAnotherCalc();
    }
    

    Run2中,你可以保证在PerformAnotherCalc之后会显示"Done"。在Run1中,在PerformAnotherCalc之前会显示"Done",但在PerformCalc之后显示(假设DoLongTaskAsync实际上是异步的,这取决于它的实现方式)。

    1
    你可以检查一下代码下面的评论。看起来你把他的示例和你的混淆了。 - Jeroen van Langen
    @JeroenvanLangen 是的,我混合了两个示例。我已经添加了缺失的部分到我的答案中,以便更容易理解。 - Kevin Gosse

    -1

    所有调用都是同步的。只有当返回的可等待对象未完成时,await 才可能是异步的。


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接