2016-01-20 21 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 - 基本類型或接口上的退貨成員,意思是可能是異步。

相關問題