C# 理解阻塞 UI 和异步/等待与 Task.Run 的问题?

Posted

技术标签:

【中文标题】C# 理解阻塞 UI 和异步/等待与 Task.Run 的问题?【英文标题】:C# Understanding trouble with blocked UI and async / await vs. Task.Run? 【发布时间】:2019-02-19 23:11:55 【问题描述】:

我正在尝试做一些与 UI 线程分离的异步 I/O 工作。我在某处读到:

1) 对于 CPU 绑定代码,您等待在 使用 Task.Run 方法的后台线程。比如计算素数 数字 2) 对于 I/O 绑定代码,您等待返回一个 异步方法中的任务或任务。比如等待 网络或数据库

所以我这样做了:

// in windows form UI
private async void btnImport_Click(object sender, EventArgs e) 
    // [...]
    List<DataRow> rows = await importer.ImportDataAsync(123, 456);
    // [...]


// in Importer.ImportDataAsync:
public async Task<List<DataRow>> ImportDataAsync(int parent, int child, CancellationToken token = default(CancellationToken)) 

    // [...]
    List<DataRow> list = await RealImportFromDB(parent, child);
    return list;
    // [...]



public List<DataRow> RealImportFromDB(int p, int c) 

    List<DataRow> rowList;
    // here fetch the rows from DB over slow network
    // and return the row list
    return rowList;

使用这种方法会阻止 UI。 如果我像这样调用 RealImportFromDB(...)

List<DataRow> l = await Task.Run(() => RealImportFromDB(parent, child));

UI 未被阻止,但与 IMHO 上方的第 2 点冲突。

我哪里做错了?

最好的问候,亚历克斯

【问题讨论】:

您还没有展示如何从数据库中读取数据。如果是同步方法,自然会阻塞。 Calling async method on button click的可能重复 RealImportFromDB 没有返回Task 时,你还等什么?它也应该是async,并且应该等待对数据库的异步方法的调用。使用 async/await 时,它应该从顶部(在您的情况下为按钮单击处理程序)一直到底部(调用数据库)。 不确定您是如何遇到运行时错误的,因为它甚至无法编译。您不能等待不返回 TaskTask&lt;T&gt; 的方法(等待 RealImportFromDB)。 【参考方案1】:

public List&lt;DataRow&gt; RealImportFromDB(int p, int c) 是对数据库的阻塞调用,因此要异步执行它,您使用了 #1,您将调用包装在 Task.Run 中,这将按预期释放 Ui 线程

使用这种方法会阻止 UI。如果我调用 RealImportFromDB(...)

由于该方法不适用于异步调用,因此它不会返回 TaskTask&lt;T&gt;,这是进行异步调用的常见要求

您的代码 await RealImportFromDB(parent, child) 不正确,这是一个编译错误,因为您只能等待调用,它实现了 GetAwaiter() 内部检查(this 和 this),最常见的情况是返回 @ 987654330@或Task&lt;T&gt;,还有其他类型的

让我们试着理解你的两个陈述:

1) 对于受 CPU 限制的代码,您等待使用 Task.Run 方法在后台线程上启动的操作。比如计算素数

这就是你当前正在做的事情,通常在客户端中完成以释放 Ui 线程,而处理发生在后台,但这仍然会使用线程池线程来执行,这不如 Ui 重要线程,但仍然是系统资源

2) 对于 I/O 绑定代码,您等待在异步方法中返回任务或任务的操作。比如等待网络或数据库

要实现这一点,您需要一个方法,默认为 Async 并返回 TaskTask&lt;T&gt;,这些方法是所有数据框架的一部分,现在每个同步方法都有一个相应的异步方法来启动异步执行,它们是真正的IO调用,它们不使用线程,因为处理不在同一个进程中,它跨越网络/进程边界,所以调用线程不需要等待,它只需要回来和选择结果,当它到达时(任何线程池线程,不需要调度线程)。此类方法在内部使用TaskCompletionSource&lt;T&gt; (When to use TaskCompletionSource),它具有在网络调用完成时通知调用者的机制

【讨论】:

【参考方案2】:

要实现这一点,您需要一个方法,默认为 Async 并且 返回一个任务或任务

非常感谢,这是我的麻烦。我的RealImportFromDB(...) 方法不是异步方法,因为它处理似乎还没有准备好进行异步调用的旧的专有库。

这些是我的想法: 等待来自ImportDataAsync(...) 的结果,在其中调用的所有内容(例如RealImportFromDB(...))都从 UI 线程调度。可以这么说:ImportDataAsync(...) 中的所有内容都封装/运行在第二个非阻塞线程中。

@others:是的,你是对的,我的代码中的示例甚至无法编译。摆弄了很多,所以代码示例没有显示所有更改的内容,对此感到抱歉:-

【讨论】:

以上是关于C# 理解阻塞 UI 和异步/等待与 Task.Run 的问题?的主要内容,如果未能解决你的问题,请参考以下文章

nginx异步非阻塞理解

使用异步等待 C# Xamarin

理解同步异步阻塞与非阻塞

怎样理解阻塞非阻塞与同步异步的区别?

同步与异步阻塞与非阻塞理解

概念理解同步异步阻塞非阻塞