为什么DbConnection.OpenAsync(CancellationToken)的默认实现是同步的?

时间:2016-01-20 22:32:17

标签: c# .net task-parallel-library

我正在阅读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()

3 个答案:

答案 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实施不使用网络怎么办?也许它只是打开一个文件。基类没有概括性。

我希望大多数提供商能够实现此方法的真正异步版本,但如果您确实需要从任何人进行异步,那么我只需将其包装在运行中。但是,如果您遇到了一个不支持真正的异步打开的提供程序,它也可能不支持线程安全打开。