异步编程:在非异步函数中使用await

3

首先是问题: 我可以在没有标记为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()不是异步的)

4个回答

3

人们往往忘记了asyncawait背后的含义。

不,你不能在一个非async方法中await,但是你可以在返回的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 代替它。 - Stephen Cleary
@StephenCleary:虽然我同意它是“低级”的,但我对“危险”的想法感到困惑。这只是一个观点还是有一些理由支持? - Wiktor Zychla
1
这是一篇漫长的讨论。总的来说,最常见的危险部分包括:它不理解异步委托,它默认不使用线程池(正如大多数人所认为的那样),它的“CancellationToken”将取消正在运行的委托。它还有默认值的参数,只有在进行动态任务并行处理时才有意义,而不是异步编程;特别是在使用它进行异步编程时,您应该始终传递“TaskScheduler”和“TaskContinuationOptions”。 - Stephen Cleary

0

如果没有将方法标记为async,则无法使用await函数调用。因此,您的第二个示例将无法编译。但是,您可以将Start方法标记为async

Async await是一种非阻塞等待执行函数的方法。在这种情况下,在GetFolderPath方法中的等待会停止执行,但同时,Start方法中的执行将继续。我认为,由于这个原因,作者使用while循环等待getFolderPath的执行完成。


0

你第一个问题的答案是不行。来自官方文档

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

如果你想要等待异步方法getFolderPath在start方法内执行完成,你需要更新签名为

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不是async,它怎么能返回Task呢?) - KansaiRobot
@KansaiRobot:确实应该是“public async Task Start()”。 - Johan Donne
是的,谢谢你指出来,我忘记添加它了。 - Daniel Ormeño
@KansaiRobot 即使方法不是异步的,你仍然可以返回任务。所有 async 做的就是启用 await(生成连续状态机)并正确处理异常。基本上,如果你想使用异步,请在整个过程中都使用异步。 - wingerse
@KansaiRobot 是的,请记住 async 只是一个关键字,而不是方法的返回类型。当将方法标记为 async 时,该方法必须返回一个 Task<T>,其中 T 是返回类型。如果您不等待方法调用,则该方法将返回一个常规任务。 - Daniel Ormeño

0

你可以显式地等待 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");
    }

这就是异步编程让我感到困惑的地方。我的意思是,一旦你完成了与调用异步函数无关的任务,并且你所要做的就是等待,那不就变成同步了吗? - KansaiRobot
1
无论如何,您都必须等待。问题是您正在运行的线程是否被阻塞。异步:您的代码在等待 await 返回期间停止执行,但线程仍然可以处理其他操作。同步:整个线程都被阻塞等待。如果您的代码运行的线程是 UI 线程,则这是最相关的。如果使用异步调用,则 UI 保持响应,如果等待阻塞,则会冻结。 - Johan Donne
@Daniel Ormeño:我知道(正如我的答案所示)。如果您需要从必须(或想要)保持同步的方法中消耗无法更改的异步方法(例如在第三方类库中),则该技术可能很有用。 - Johan Donne

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