【问题标题】:Asynchronous Programming: await inside a function that is not async异步编程:在非异步函数中等待
【发布时间】:2017-09-01 19:44:48
【问题描述】:

首先是问题: 我可以在未标记为 async 的函数中使用 await 吗??

现在是细节。我正在读这篇文章 Hololens- Capturing Photo...,正如你所看到的,作者发布了一些代码。 其中这个

 void Start ()
    {
        getFolderPath();
        while (!haveFolderPath)
        {
            Debug.Log("Waiting for folder path...");
        }
        Debug.Log("About to call CreateAsync");
        PhotoCapture.CreateAsync(false, OnPhotoCaptureCreated);
        Debug.Log("Called CreateAsync");
    }


    async void getFolderPath()
    {
        StorageLibrary myPictures = await Windows.Storage.StorageLibrary.GetLibraryAsync(Windows.Storage.KnownLibraryId.Pictures);
        Windows.Storage.StorageFolder savePicturesFolder = myPictures.SaveFolder;
        Debug.Log("savePicturesFolder.Path is " + savePicturesFolder.Path);
        folderPath = savePicturesFolder.Path;
        haveFolderPath = true;
    }

现在请注意 getFolderPath 如何返回 void(作为事件处理程序),但文档说这些方法不能等待。作者改为使用 while 循环等待。

如果我这样做会怎样

     void Start ()
        {
            await getFolderPath();

            Debug.Log("About to call CreateAsync");
            PhotoCapture.CreateAsync(false, OnPhotoCaptureCreated);
            Debug.Log("Called CreateAsync");
        }


     //Notice how now it returns Task
        async Task getFolderPath()
        {
            StorageLibrary myPictures = await Windows.Storage.StorageLibrary.GetLibraryAsync(Windows.Storage.KnownLibraryId.Pictures);
//.....
        }

我可以这样做吗? (注意 Start() 不是异步的)

【问题讨论】:

    标签: c# asynchronous


    【解决方案1】:

    人们往往会忘记 asyncawait 背后的内容。

    不,您不能 await 在不是async 的方法中,但您可以在返回的Task 上调用ContinueWith 并提供仅在任务执行时执行的显式延续已完成:

    class Example
    {
        public void Start()
        {
            getFolderPath()
               .ContinueWith(t =>
               {                   
                   Console.WriteLine("...");
               });
        }
    
        async Task getFolderPath()
        {
            await Task.Delay(1000);
        }
    }
    

    相当于

    class Example
    {
        public async Task Start()
        {
            await getFolderPath();
            Console.WriteLine("...");
        }
    
    
        async Task getFolderPath()
        {
            await Task.Delay(1000);
        }
    }
    

    【讨论】:

    • 技术上是正确的,但ContinueWith 是一个低级的、危险的 API。您可以(并且应该)改用await
    • @StephenCleary:虽然我同意它是“低级的”,但我为“危险”而挠头。这只是一种观点还是背后有一些理由?
    • Here's a lengthy discussion。总之,最常见的危险部分是:它不理解异步委托,默认情况下不使用线程池(大多数人认为),它的CancellationToken 将取消正在运行的委托。它的参数也有默认值,只有在你进行动态任务并行时才有意义,不是异步编程;特别是,在将其用于异步编程时,您应该始终传递 TaskSchedulerTaskContinuationOptions
    【解决方案2】:

    如果不将方法标记为async,则不能调用await 函数。所以你的第二个例子不会编译。但是您可以将 Start 方法标记为async

    Async await 是一种等待函数执行的非阻塞方法。在这种情况下,执行将在 GetFolderPath 方法中的 await 处停止,但是在等待时,Start 方法中的执行将继续。我假设,因此,作者使用while loop 等待getFolderPath 的执行完成。

    【讨论】:

      【解决方案3】:

      您可以显式等待GetFolderPath 方法完成:

          void Start ()
          {
              getFolderPath().Wait();
      
              Debug.Log("About to call CreateAsync");
              PhotoCapture.CreateAsync(false, OnPhotoCaptureCreated);
              Debug.Log("Called CreateAsync");
          }
      
      
       //Notice how now it returns Task
          async Task getFolderPath()
          {
              StorageLibrary myPictures = await Windows.Storage.StorageLibrary.GetLibraryAsync(Windows.Storage.KnownLibraryId.Pictures);
              //.....
          }
      

      但是您将异步方法转换为同步方法,因为Wait 是一种阻塞方法(但无论如何,这都是在 while 循环中发生的)。

      如果您的目标是保持操作异步,则必须遵循 Daniel Ormeño 的建议:

          async Task Start ()
          {
              await getFolderPath();
      
              Debug.Log("About to call CreateAsync");
              PhotoCapture.CreateAsync(false, OnPhotoCaptureCreated);
              Debug.Log("Called CreateAsync");
          }
      

      【讨论】:

      • 这就是异步编程让我感到困惑的地方。我的意思是,一旦你完成了与调用的异步函数无关的事情,你所要做的就是等待,它就变成了同步的,不是吗?
      • 无论如何你都要等待。问题是您正在运行的线程是否被阻塞。异步:您的代码在 await 返回之前停止执行,但线程仍然可以处理其他操作。同步:整个线程被阻塞等待。如果您的代码在其中运行的线程是 UI 线程,则这一点最为相关。如果您使用异步调用,UI 将保持响应,如果您等待阻塞,它会冻结。
      • @Daniel Ormeño:我知道(如我的回答中所示)。如果您需要使用无法从您必须(或想要)保持同步的方法中更改的异步方法(例如,在第 3 部分类库中),该技术可能很有用。
      【解决方案4】:

      第一个问题的答案是不,你不能。来自官方documentation

      await只能用在async修改的异步方法中 关键词。这样的方法,通过使用 async 修饰符和 通常包含一个或多个等待表达式,被称为 异步方法。

      如果要在start方法中等待async方法getFolderPath的执行,需要更新签名为

      public async Task Start ()
      {
          await getFolderPath();
      
          Debug.Log("About to call CreateAsync");
          PhotoCapture.CreateAsync(false, OnPhotoCaptureCreated);
          Debug.Log("Called CreateAsync");
      }
      

      async await上下文中,Task作为返回类型,表示任务不返回值,相当于同步方式中的void。例如,如果您需要从异步任务返回 string,则需要有 public async Task<string> GetFoo()

      总的来说,我认为您正在查看的示例中的代码需要一些审查。

      【讨论】:

      • 谢谢。但是,那里不是 async 和 Task 吗? (注意 getFolderPath 在我上面的代码中是如何返回 Task 的。我想我理解你所说的等效性。但是如果 Start 不是异步的,它怎么能返回 Task 呢?
      • @KansaiRobot:确实,应该是'public async Task Start()'。
      • 是的,谢谢你的指出,我忘了添加包含它
      • @KansaiRobot 即使方法不是异步的,您也可以返回 Task。所有async 所做的只是启用await(它为继续生成状态机)并正确处理异常。基本上,如果你想使用异步,就一直使用异步。
      • @KansaiRobot 是的,请记住 async 只是一个关键字,它不是方法的返回类型。将方法标记为异步时,该方法必须返回 Task<T>,其中 T 是返回类型。如果您不等待方法调用,则该方法将返回一个常规任务。
      猜你喜欢
      • 2018-04-23
      • 2017-04-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-22
      • 2023-03-13
      • 2023-03-12
      • 1970-01-01
      相关资源
      最近更新 更多