调用异步方法和 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());
}
}
一切都很好。他们不应该是一样的吗?
我在想不使用 await 来调用异步方法会创建一个新线程。
Should not they be the same? I was thinking that not using await for
calling an async method creates a new thread.
不,async
不会神奇地为其方法调用分配新线程。 async-await
主要是关于利用自然异步 API,例如对数据库或远程 Web 服务的网络调用。
当您使用 Task.Run
时,您明确使用线程池线程来执行您的委托。如果您使用 async
关键字标记一个方法,但在内部不 await
任何东西,它将同步执行。
我不确定你的 SyncContacts()
方法实际上做了什么(因为你没有提供它的实现),但是标记它 async
本身不会给你任何好处。
编辑:
现在您已经添加了实现,我看到两件事:
- 我不确定您的同步数据分析 CPU 有多密集,但这可能足以让 UI 无响应。
您没有在等待异步操作。它需要看起来像这样:
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());
真正做的是创建一个新任务来启动它,然后 return 将它发送给调用者(在你的情况下不会用于任何进一步的目的)。这就是为什么它会在后台工作并且 UI 会继续工作的原因。如果您需要 (a) 等待任务完成,您可以使用 await Task.Run(() => this.SyncContacts());
。如果您只想确保 SyncContacts 在您 return 您的 SyncData 方法时完成,您可以使用 returning 任务并在 SyncData 方法结束时等待它。正如评论中所建议的那样:如果您对任务是否已完成不感兴趣,您可以 return 它。
但是,Microsoft 建议不要混合使用阻塞代码和异步代码,并且异步方法以 Async (https://msdn.microsoft.com/en-us/magazine/jj991977.aspx) 结尾。因此,当您不使用 await 关键字时,您应该考虑重命名您的方法并且不要将方法标记为 async。
只是为了澄清 UI 冻结的原因 - 在紧密的 foreach
循环中完成的工作很可能 CPU-bound 并且会阻塞原始调用者的线程直到循环完成。
因此,无论 SyncContacts
返回的 Task
是否被 await
编辑,调用 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 await
在 SyncContacts
- 参见 Yuval 的观点 - 使方法异步并等待结果本来是 wasteful in this instance)
对于 WPF 项目,使用 Task.Run
在调用线程之外执行 CPU 绑定工作应该没问题(但 not so for MVC or WebAPI Asp.Net 项目)。
此外,假设 contactsToUpload
映射工作是线程安全的,并且您的应用充分利用了用户的资源,您还可以考虑并行化映射以减少整体执行时间:
var contactsToUpload = this.AllContacts
.AsParallel()
.Select(contact => MapToUploadContact(contact));
// or simpler, .Select(MapToUploadContact);
我的视图模型中有一个方法
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 来调用异步方法会创建一个新线程。
Should not they be the same? I was thinking that not using await for calling an async method creates a new thread.
不,async
不会神奇地为其方法调用分配新线程。 async-await
主要是关于利用自然异步 API,例如对数据库或远程 Web 服务的网络调用。
当您使用 Task.Run
时,您明确使用线程池线程来执行您的委托。如果您使用 async
关键字标记一个方法,但在内部不 await
任何东西,它将同步执行。
我不确定你的 SyncContacts()
方法实际上做了什么(因为你没有提供它的实现),但是标记它 async
本身不会给你任何好处。
编辑:
现在您已经添加了实现,我看到两件事:
- 我不确定您的同步数据分析 CPU 有多密集,但这可能足以让 UI 无响应。
您没有在等待异步操作。它需要看起来像这样:
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());
真正做的是创建一个新任务来启动它,然后 return 将它发送给调用者(在你的情况下不会用于任何进一步的目的)。这就是为什么它会在后台工作并且 UI 会继续工作的原因。如果您需要 (a) 等待任务完成,您可以使用 await Task.Run(() => this.SyncContacts());
。如果您只想确保 SyncContacts 在您 return 您的 SyncData 方法时完成,您可以使用 returning 任务并在 SyncData 方法结束时等待它。正如评论中所建议的那样:如果您对任务是否已完成不感兴趣,您可以 return 它。
但是,Microsoft 建议不要混合使用阻塞代码和异步代码,并且异步方法以 Async (https://msdn.microsoft.com/en-us/magazine/jj991977.aspx) 结尾。因此,当您不使用 await 关键字时,您应该考虑重命名您的方法并且不要将方法标记为 async。
只是为了澄清 UI 冻结的原因 - 在紧密的 foreach
循环中完成的工作很可能 CPU-bound 并且会阻塞原始调用者的线程直到循环完成。
因此,无论 SyncContacts
返回的 Task
是否被 await
编辑,调用 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 await
在 SyncContacts
- 参见 Yuval 的观点 - 使方法异步并等待结果本来是 wasteful in this instance)
对于 WPF 项目,使用 Task.Run
在调用线程之外执行 CPU 绑定工作应该没问题(但 not so for MVC or WebAPI Asp.Net 项目)。
此外,假设 contactsToUpload
映射工作是线程安全的,并且您的应用充分利用了用户的资源,您还可以考虑并行化映射以减少整体执行时间:
var contactsToUpload = this.AllContacts
.AsParallel()
.Select(contact => MapToUploadContact(contact));
// or simpler, .Select(MapToUploadContact);