繁体   English   中英

调用异步方法和Task.Run异步方法之间的区别

[英]Difference between calling an async method and Task.Run an async method

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

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()); 
    }
}

一切顺利。 他们不应该一样吗? 我在想,不使用等待调用异步方法会创建一个新线程。

他们不应该一样吗? 我在想,不使用等待调用异步方法会创建一个新线程。

不, 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( https://msdn.microsoft.com/zh-cn/magazine/jj991977.aspx )结尾。 因此,当您不使用await关键字时,应该考虑重命名方法,并且不要使用异步标记方法。

只是为了阐明UI冻结的原因-在紧密的foreach循环中完成的工作可能受CPU限制,并且将阻塞原始调用者的线程,直到循环完成。

因此,无论是否awaitSyncContacts返回的Task ,调用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);
}

(Re:不,为什么async / return awaitSyncContactsasync / return await SyncContacts请参见Yuval的要点-使方法异步并等待结果在这种情况下会很浪费

对于WPF项目,应该可以使用Task.Run在调用线程Task.Run完成CPU绑定工作(但对于MVC或WebAPI Asp.Net项目则不是这样 )。

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM