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

时间:2015-08-12 07:01:19

标签: c# xaml asynchronous async-await task-parallel-library

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

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来调用异步方法会创建一个新线程。

3 个答案:

答案 0 :(得分:9)

  

他们不应该一样吗?我以为没有使用等待   调用异步方法会创建一个新线程。

不,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);
    }
    

答案 1 :(得分:0)

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

但是,Microsoft建议不要混用阻止代码和异步代码,并且async方法以Async(https://msdn.microsoft.com/en-us/magazine/jj991977.aspx)结尾。因此,在不使用await关键字时,应考虑重命名方法,不要使用async标记方法。

答案 2 :(得分:0)

只是为了澄清UI冻结的原因 - 在紧密find ./ -iname "*.jpg" 循环中完成的工作很可能是CPU绑定的,并且会阻塞原始调用者的线程,直到循环完成。

因此,无论foreach返回的Task是否为SyncContacts,在调用await之前的CPU绑定工作仍将同步发生,并且阻止,调用者的线程。

AddContactsAsync

(回复:为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 await上的SyncContacts - 请参阅Yuval的观点 - 让方法异步并等待结果将是wasteful in this instance

对于WPF项目,可以使用Task.Run从调用线程(但是not so for MVC or WebAPI Asp.Net项目)执行CPU绑定工作。

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

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