2016-01-20 22 views
3

我读documentation for DbConnection.OpenAsync(CancellationToken),发现下面的代码片段:为什么DbConnection.OpenAsync(CancellationToken)的默认实现是同步的?

的默认实现调用同步Open调用和返回完成的任务。如果传递一个已经取消的cancellationToken,默认实现将返回一个取消的任务。 Open引发的异常将通过返回的Task Exception属性进行通信。现在

,如果我是一个参差不齐/慢速网络连接,并使用尚未覆盖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(); // Convenience wrapper around OpenAsync(CancellationToken) 
      button1.Text = "Opened successfully!"; 
     } 
     catch (Exception ex) 
     { 
      button1.Text = ex.Message; 
     } 
    } 
} 

而连接如果提供者没有重写默认实现,则正在建立。为了防止这种情况发生,无论底层数据库提供者如何,我不妨做await Task.Run(async() => await connection.OpenAsync());。为什么采用这种方式实现默认实现,以及如何在不编写提供程序感知代码的情况下知道何时需要Task.Run()

回答

4

您的await Task.Run(async() => await connection.OpenAsync())不会在同一个线程中执行connection.OpenAsync(),但connection.OpenAsync()connection.Open()依赖于线程本地状态是完全合理的。例如,他们可能并且通常应该注意Transaction.Current。如果.NET Framework在后台线程中静默执行connection.Open(),则某些人会得到非常错误的结果。

+0

这种隐式事务管理API看起来使用起来很危险,并且喜欢某些事情以避免对我... – binki

+0

啊,我明白了,你是说因为某些提供程序在打开连接时使用线程本地存储,所以' DbConnection' *不能*安全地使用线程池来使它立即返回。 – binki

+0

@binki就像在Transaction.Current文档中说的那样,你通常不直接使用它。你使用'TransactionScope'来管理它,然后它变成一个非常有用的机制。连接类本身直接使用'Transaction.Current',如果在后台线程上调用,则不会看到预期的值。我没有提到使用线程本地存储的连接类本身(我只想到其他类),但是你是对的,那是另一种有效的可能性。 – hvd

0

的关键短语的文档是

提供商应该提供适当的实现覆盖。

DbConnection是实现特定类的基类。它不知道如何使它异步的底层实现。基类开发人员选择为没有实现自己的异步版本的提供者提供一个简单的实现。

我同意,实现不是一个伟大的,但你可以做的所有你不知道底层的实现。

如果Open实现不使用网络会怎么样?也许它只是打开一个文件。没有可以由基类进行的泛化。

我希望大多数供应商实现该方法的实际异步版本,但是如果你真的需要异步从,然后我只是把它包在运行。但是,如果您运行的提供程序不支持真正的异步打开,它可能不会支持线程安全打开。

+0

打开一个文件仍然是一个阻塞操作,对于该操作,异步API存在,并且即使人们推荐它,使用线程池的帮助“伪造”异步访问也会起作用。 – binki

+1

是的,打开一个文件有异步API,但DbConnection类不知道这是怎么回事。一个使用文件访问的提供者在实现一个合适的OpenAsync的过程中会花费很少的时间,对于基类来说这是不可能的。使用线程池来伪装它将会起作用,除非Open像在线程本地存储中那样放置文件句柄,否则它根本无法工作。就个人而言,如果Open可能是一项耗时的操作,我只会假设提供者正确实施了OpenAsync。 –

0

为什么是默认的实现这样

一句话:向后兼容性。在理想世界中,ConnectAsync将是一个抽象方法;然而,这是不可能的,因为在async出现的时候已经有很多DbConnection实现。

因此,DbConnection的设计者不得不选择同步或伪异步(线程池)实现。这两种选择都不能提供很好的最终用户体验。

对于一个有趣的反例,请考虑Stream。这是另一个面临相同问题的常见基类,但做出了相反的选择(即基线Stream.ReadAsync实现从线程池调用Stream.Read)。

以及如何在不编写提供程序感知代码的情况下知道何时需要Task.Run()?

不幸的是,这是不可能的。您必须考虑Task - 基本类型或接口上的退货成员,意思是可能是异步。

相关问题