为什么 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 返回成员视为意味着 可能 异步。