微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

使用 ConfigureAwait(false) 或 Task.Run 避免阻塞 UI 线程

如何解决使用 ConfigureAwait(false) 或 Task.Run 避免阻塞 UI 线程

我正在尝试找出哪种方法更好,假设我们有一个按钮,在用户点击它后我们执行 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.RunTask 的方法可能仍会阻塞调用线程,具体取决于其实现。通常,这可能是使用在后台线程上调用这两种方法的第一种方法以避免阻塞 UI 线程的原因。但是,如果您使用实现良好的 API(例如 Task<T>),这不是问题。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。