问题描述
我正在尝试找出哪种方法更好,假设我们有一个按钮,在用户点击它后我们执行 1. 使用 httpClient
发送异步请求
2. 一些繁重的同步工作人员,喜欢计算和将数据保存到数据库中。
像这样:
button1.Click += async(sender,e) =>
{
bool a = await Task.Run(async () => { return await MyTask1();});
}
async Task<bool> MyTask1()
{
await new HttpClient().GetAsync("https://www.webpage.com");
DoHeavyStuffFor5minutes();
return true;
}
button2.Click += async(sender,e) =>
{
bool a = await MyTask2();
}
async Task<bool> MyTask2()
{
await new HttpClient().GetAsync("https://www.webpage.com").ConfigureAwait(false);
DoHeavyStuffFor5minutes();
}
据我所知,GetAsync
不会阻塞我的 UI 线程,因为在它下面使用一种方法使其在不同的线程上运行,可能是 Task.Run
或任何其他允许这样做的方法。
但是 DoHeavyStuffFor5Minutes
会阻塞我的 UI,因为它会在调用方 SynchornizationContext
上被调用。
所以我读到使用 ConfigureAwait(false)
会使 GetAsync
之后的代码不会在与调用者相同的 SynchornizationContext 上运行。我的问题是,第一种方法还是第二种方法更好?
解决方法
无需使用 HttpClient.GetAsync
在后台线程上执行 Task.Run
,因为 HTTP 请求本质上是真正异步的,因此在这种情况下,您的第二种方法比第一种方法更好。
当 Task
返回的 GetAsync
最终完成时,其余部分或 MyTask2()
将在线程池线程上执行,假设您通过调用 {{1 }}。
但是请注意,ConfigureAwait(false)
并不能保证在所有情况下都不会在原始上下文中运行回调或剩余器。
来自 Stephen Toub 的 blog post:
ConfigureAwait(false) 是否保证回调不会在原始上下文中运行?
“不。它保证它不会排队回到原始上下文......但这并不意味着 ConfigureAwait(false)
之后的代码不会仍然在原始上下文中运行。那是因为等待已完成的等待对象只是同步运行通过 await task.ConfigureAwait(false)
而不是强制任何东西排队。所以,如果你 await
一个在等待时已经完成的任务,不管无论您是否使用了 await
,紧接在此之后的代码将继续在当前线程中的任何上下文中继续执行。”
因此,为了安全起见,您可能希望使用 ConfigureAwait(false)
将 DoHeavysTuffFor5minutes
卸载到后台线程,我认为它是一个受 CPU 限制且可能需要长时间运行的操作。至少在一般情况下是这样。
另请注意,名为 *Async 并返回 Task.Run
或 Task
的方法可能仍会阻塞调用线程,具体取决于其实现。通常,这可能是使用在后台线程上调用这两种方法的第一种方法以避免阻塞 UI 线程的原因。但是,如果您使用实现良好的 API(例如 Task<T>
),这不是问题。