为什么 DbConnection.OpenAsync(CancellationToken) 的默认实现是同步的?
Why is the default implementation of DbConnection.OpenAsync(CancellationToken) synchronous?
我正在阅读 documentation for DbConnection.OpenAsync(CancellationToken)
并发现以下片段:
The default implementation invokes the synchronous Open call and returns a completed task. The default implementation will return a cancelled task if passed an already cancelled cancellationToken. Exceptions thrown by Open will be communicated via the returned Task Exception property.
现在,如果我使用 spotty/slow 网络连接并使用未覆盖 DbConnection.OpenAsync(CancellationToken)
的数据库提供程序(即,我使用的不是 System.Data.SqlClient
) , 和 如果我将其放入 UI 按钮的事件处理程序中,例如:(假设代码,未经测试)
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()
?
文档中的关键词是
Providers should override with an appropriate implementation.
DbConnection 是实现特定 classes 的基础 class。它不知道底层实现,不知道如何使其异步。基础 class 开发人员选择为未实现自己的异步版本的提供程序提供简单的实现。
我同意,这个实现不是很好,但是在不了解底层实现的情况下你可以做的几乎所有事情。
如果 Open 实现不使用网络怎么办?也许它只是打开一个文件。基础 class.
无法概括
我希望大多数提供者实现此方法的真正异步版本,但如果您确实需要与 任何人 异步,那么我会将其包装在运行。但是,如果您 运行 跨越不支持真正异步打开的提供程序,它很可能也不支持线程安全打开。
你的 await Task.Run(async () => await connection.OpenAsync())
不会在同一个线程中执行 connection.OpenAsync()
,但是 connection.OpenAsync()
和 connection.Open()
依赖于 thread-local 状态是完全合理的.例如,他们可能而且通常应该注意 Transaction.Current
。如果 .NET Framework 在后台线程中静默执行 connection.Open()
,有些人会得到非常错误的结果。
Why is the default implementation this way
一句话:向后兼容。在理想世界中,ConnectAsync
将是一个抽象方法;然而,这是不可能的,因为到 async
出现时已经有很多 DbConnection
实施。
因此,DbConnection
的设计者不得不选择同步或 fake-asynchronous(线程池)实现。这两种选择都无法提供出色的 end-user 体验。
对于一个有趣的反例,请考虑 Stream
。这是另一个面临相同问题,但做出相反选择的公共基础class(即基础Stream.ReadAsync
实现从线程池调用Stream.Read
)。
and how is one supposed to know when Task.Run() is needed without writing provider-aware code?
不幸的是,这是不可能的。您必须将基类型或接口上的 Task
返回成员视为意味着 可能 异步。
我正在阅读 documentation for DbConnection.OpenAsync(CancellationToken)
并发现以下片段:
The default implementation invokes the synchronous Open call and returns a completed task. The default implementation will return a cancelled task if passed an already cancelled cancellationToken. Exceptions thrown by Open will be communicated via the returned Task Exception property.
现在,如果我使用 spotty/slow 网络连接并使用未覆盖 DbConnection.OpenAsync(CancellationToken)
的数据库提供程序(即,我使用的不是 System.Data.SqlClient
) , 和 如果我将其放入 UI 按钮的事件处理程序中,例如:(假设代码,未经测试)
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()
?
文档中的关键词是
Providers should override with an appropriate implementation.
DbConnection 是实现特定 classes 的基础 class。它不知道底层实现,不知道如何使其异步。基础 class 开发人员选择为未实现自己的异步版本的提供程序提供简单的实现。
我同意,这个实现不是很好,但是在不了解底层实现的情况下你可以做的几乎所有事情。
如果 Open 实现不使用网络怎么办?也许它只是打开一个文件。基础 class.
无法概括我希望大多数提供者实现此方法的真正异步版本,但如果您确实需要与 任何人 异步,那么我会将其包装在运行。但是,如果您 运行 跨越不支持真正异步打开的提供程序,它很可能也不支持线程安全打开。
你的 await Task.Run(async () => await connection.OpenAsync())
不会在同一个线程中执行 connection.OpenAsync()
,但是 connection.OpenAsync()
和 connection.Open()
依赖于 thread-local 状态是完全合理的.例如,他们可能而且通常应该注意 Transaction.Current
。如果 .NET Framework 在后台线程中静默执行 connection.Open()
,有些人会得到非常错误的结果。
Why is the default implementation this way
一句话:向后兼容。在理想世界中,ConnectAsync
将是一个抽象方法;然而,这是不可能的,因为到 async
出现时已经有很多 DbConnection
实施。
因此,DbConnection
的设计者不得不选择同步或 fake-asynchronous(线程池)实现。这两种选择都无法提供出色的 end-user 体验。
对于一个有趣的反例,请考虑 Stream
。这是另一个面临相同问题,但做出相反选择的公共基础class(即基础Stream.ReadAsync
实现从线程池调用Stream.Read
)。
and how is one supposed to know when Task.Run() is needed without writing provider-aware code?
不幸的是,这是不可能的。您必须将基类型或接口上的 Task
返回成员视为意味着 可能 异步。