调用异步方法和调用Task之间的区别.运行一个异步方法

本文关键字:调用 异步方法 一个 区别 Task 之间 运行 | 更新日期: 2023-09-27 18:07:22

我的视图模型中有一个方法

private async void SyncData(SyncMessage syncMessage)
{
    if (syncMessage.State == SyncState.SyncContacts)
    {
        this.SyncContacts(); 
    }
}
private async Task SyncContacts()
{
    foreach(var contact in this.AllContacts)
    {
       // do synchronous data analysis
    }
    // ...
    // AddContacts is an async method
    CloudInstance.AddContacts(contactsToUpload);
}

当我从UI命令调用SyncData时,我正在同步一大块数据UI冻结。但是当我用这种方法调用SyncContacts

private void SyncData(SyncMessage syncMessage)
{
    if (syncMessage.State == SyncState.SyncContacts)
    {
        Task.Run(() => this.SyncContacts()); 
    }
}

一切正常。它们不应该是一样的吗?我在想,不使用await调用异步方法会创建一个新线程。

调用异步方法和调用Task之间的区别.运行一个异步方法

它们不应该是相同的吗?我在想不使用await for调用异步方法会创建一个新线程。

不,async不会神奇地为它的方法调用分配一个新线程。async-await主要是利用自然异步api,例如对数据库或远程web服务的网络调用。

当你使用Task.Run时,你显式地使用线程池线程来执行委托。如果你用async关键字标记一个方法,但在内部没有await任何东西,它将同步执行。

我不确定你的SyncContacts()方法实际上做了什么(因为你没有提供它的实现),但是把它标记为async本身不会给你带来任何好处。

编辑:

现在您已经添加了实现,我看到了两件事:

  1. 我不确定你的同步数据分析的CPU密集程度,但它可能足以让UI无响应。
  2. 您没有等待异步操作。它需要看起来像这样:

    private async Task SyncDataAsync(SyncMessage syncMessage)
    {
        if (syncMessage.State == SyncState.SyncContacts)
        {
            await this.SyncContactsAsync(); 
        }
    }
    private Task SyncContactsAsync()
    {
        foreach(var contact in this.AllContacts)
        {
           // do synchronous data analysis
        }
        // ...
        // AddContacts is an async method
        return CloudInstance.AddContactsAsync(contactsToUpload);
    }
    

您的Task.Run(() => this.SyncContacts());行真正做的是创建一个新任务,启动它并将其返回给调用者(在您的情况下不用于任何其他目的)。这就是为什么它会在后台工作UI会继续工作。如果需要(a)等待任务完成,可以使用await Task.Run(() => this.SyncContacts());。如果你只是想确保当你返回SyncData方法时SyncContacts已经完成,你可以使用返回任务并在SyncData方法结束时等待它。正如评论中建议的那样:如果你对任务是否完成不感兴趣,你可以返回它。

但是,Microsoft建议不要将阻塞代码和async代码混合使用,并且async方法以async结束(https://msdn.microsoft.com/en-us/magazine/jj991977.aspx)。因此,当你不使用await关键字时,你应该考虑重命名你的方法,不要用async标记方法。

只是为了澄清为什么UI会冻结-在紧密的foreach循环中完成的工作可能是cpu限制的,并且会阻塞原始调用者的线程,直到循环完成。

所以,不管从SyncContacts返回的Task是否为await ed,在调用AddContactsAsync之前的CPU绑定工作仍然会同步地发生在调用者的线程上,并阻塞调用者的线程。

private Task SyncContacts()
{
    foreach(var contact in this.AllContacts)
    {
       // ** CPU intensive work here.
    }
    // Will return immediately with a Task which will complete asynchronously
    return CloudInstance.AddContactsAsync(contactsToUpload);
}

(回复:不知道为什么async / return awaitSyncContacts上-参见Yuval的观点-在这种情况下,使方法异步并等待结果将是浪费)

对于WPF项目,使用Task.Run在调用线程之外执行CPU绑定工作应该是可以的(但对于MVC或WebAPI Asp不是这样)。网项目)。

同样,假设contactsToUpload映射工作是线程安全的,并且你的应用程序充分利用了用户的资源,你也可以考虑并行化映射以减少总体执行时间:

var contactsToUpload = this.AllContacts
    .AsParallel()
    .Select(contact => MapToUploadContact(contact)); 
    // or simpler, .Select(MapToUploadContact);