我正在阅读documentation for DbConnection.OpenAsync(CancellationToken)
并找到以下代码段:
默认实现调用同步Open调用并返回已完成的任务。如果传递已取消的cancellationToken,则默认实现将返回已取消的任务。 Open抛出的异常将通过返回的Task Exception属性进行通信。
现在,如果我处于一个不稳定/慢速的网络连接并且使用了未覆盖DbConnection.OpenAsync(CancellationToken)
的数据库提供程序(即,我使用的是System.Data.SqlClient
以外的其他内容),和如果我把它插入UI Button的事件处理程序,如:(假设代码,未经测试)
async void button1_Clicked(object sender, EventArgs e)
{
using (var connection = MyProviderFactory.CreateConnection())
{
button1.Text = "Opening…";
connection.ConnectionString = _SomeString;
try
{
await connection.OpenAsync(default);
button1.Text = "Opened successfully!";
}
catch (Exception ex)
{
button1.Text = ex.Message;
}
}
}
根据我引用的文档,如果连接花了足够长的时间来完成,如果提供程序没有覆盖默认实现,则在建立连接时我的表单将是“(无响应)”。为了防止这种情况发生,无论基础数据库提供程序如何,我都可以await Task.Run(async () => await connection.OpenAsync());
。为什么默认实现是这样的?如果不编写提供者感知代码,应该知道何时需要Task.Run()
?
答案 0 :(得分:4)
您的await Task.Run(async () => await connection.OpenAsync())
不会在同一个帖子中执行connection.OpenAsync()
,但connection.OpenAsync()
和connection.Open()
依赖于线程本地状态是完全合理的。例如,他们可能并且通常应该注意Transaction.Current
。如果.NET Framework在后台线程中以静默方式执行connection.Open()
,则某些人会得到非常错误的结果。
答案 1 :(得分:1)
为什么默认实现方式
总之:向后兼容性。在理想世界中,ConnectAsync
将是一种抽象方法;但是,这是不可能的,因为当DbConnection
出现在场时已经有很多async
个实现。
因此,DbConnection
的设计者必须选择同步或伪异步(线程池)实现。这两种选择都没有提供出色的最终用户体验。
对于一个有趣的反例,请考虑Stream
。这是另一个面临同样问题的常见基类,但做出了相反的选择(即,基类Stream.ReadAsync
实现从线程池中调用Stream.Read
。
如何在不编写提供者感知代码的情况下知道何时需要Task.Run()?
不幸的是,这是不可能的。您必须将基类型或接口上的Task
- 返回成员视为可能异步。
答案 2 :(得分:0)
文档中的关键词是
提供商应该覆盖适当的实施。
DbConnection是实现特定类的基类。它不知道如何使其异步的底层实现。基类开发人员选择为未实现自己的Async版本的提供程序提供简单的实现。
我同意,实施并不是一个很好的实施,但是在不知道底层实施的情况下,您可以做的就是所有。
如果Open实施不使用网络怎么办?也许它只是打开一个文件。基类没有概括性。
我希望大多数提供商能够实现此方法的真正异步版本,但如果您确实需要从任何人进行异步,那么我只需将其包装在运行中。但是,如果您遇到了一个不支持真正的异步打开的提供程序,它也可能不支持线程安全打开。