2014-03-19 27 views
4

試試這個。安裝新的Windows窗體應用程序有一個按鈕,點擊按鈕單擊事件下面的代碼:FileStream.ReadAsync有時會同步完成嗎?

private async void button1_Click(object sender, EventArgs e) 
{ 
    using (var file = File.OpenRead(@"C:\Temp\Sample.txt")) 
    { 
     byte[] buffer = new byte[4096]; 
     int threadId = Thread.CurrentThread.ManagedThreadId; 
     int read = await file.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false); 
     Debug.Assert(threadId != Thread.CurrentThread.ManagedThreadId); 
    } 
} 

然後運行該應用程序,然後單擊按鈕迅速。如果您的經歷與我的一樣,您會發現有時按預期工作,但其他時間Debug.Assert將失敗。

根據我的ConfigureAwaitas explained on Stephen Cleary’s blog理解,通過對false應該continueOnCapturedContext指示不同步回「主」上下文(在這種情況下,UI線程)和執行應該繼續線程池的任務。

爲什麼然後斷言會隨機失敗?我只能假設ReadAsync有時不會在後臺線程上完成,即它將同步完成。

此行爲是符合KB 156932 - Asynchronous Disk I/O Appears as Synchronous on Windows

大多數I/O驅動程序(磁盤,通信和其他人)有特殊情況 碼的地方,如果一個I/O請求就可以完成「立即」,將完成 操作並且ReadFile或WriteFile函數 將返回TRUE。在所有方面,這些類型的操作似乎是同步的 。對於磁盤設備,通常,當數據緩存在內存中時,「立即」完成I/O請求。

我的假設和測試應用程序是否正確? ReadAsync方法有時可以同步完成嗎?有沒有辦法保證執行將始終在後臺線程上繼續?

此問題對我的應用程序造成嚴重破壞,該應用程序使用COM對象,這些對象要求我始終知道哪個線程正在執行。

的Windows 7 64位,.NET 4.5.1

+1

是否有原因,您不是在異步模式下打開文件? –

+0

當使用指定「異步模式」的「FileStream」重載打開文件時,我發現上述行爲未發生任何變化。 – Jacob

回答

3

ReadAsync方法有時可以同步完成嗎?

是。由於操作系統級別和.NET流類型中的緩衝區,這並不罕見。

有沒有辦法保證執行將始終在後臺線程上繼續?

如果你總是希望代碼來執行一個線程池線程,然後使用Task.Run

private async void button1_Click(object sender, EventArgs e) 
{ 
    using (var file = File.OpenRead(@"C:\Temp\Sample.txt")) 
    { 
    byte[] buffer = new byte[4096]; 
    int threadId = Thread.CurrentThread.ManagedThreadId; 
    int read = await file.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false); 
    await Task.Run(() => 
    { 
     Debug.Assert(threadId != Thread.CurrentThread.ManagedThreadId); 
    }).ConfigureAwait(false); 
    } 
} 

ConfigureAwait是一種優化的提示,而不是移動到後臺線程的命令。如果操作已完成,則ConfigureAwait不起作用。在這種情況下,await跟在"fast path"之後,實質上意味着它繼續同步,因此忽略任何ConfigureAwait提示。

+0

謝謝斯蒂芬。那麼我是否認爲'ConfigureAwait'有點誤導?我在MSDN上找不到任何東西,這讓我覺得這不僅僅是一個事實。例如,你的代碼示例在'Task.Run'上使用'ConfigureAwait(false)'。我有什麼保證「Task.Run」將在預期的上下文中完成,而不是「FileStream.ReadAsync」使用相同的方法? – Jacob

+0

我的代碼在沒有'ConfigureAwait'調用的情況下完全一樣;它會稍微低效。這就是保證在線程池線程上運行的'Task.Run'裏面*的內容,而不是任何延續。 –

2

如果文件讀取同步完成呼叫前等待,我相當肯定,沒有任何的異步編譯奇蹟發生,並執行同步繼續主叫線。如果我想確保它在線程池線程上啓動,我會使用Task.Run(() => file.ReadAsync(buffer, 0, buffer.Length))將工作排隊到線程池。

0

當您在Task上使用await時,如果任務已完成,則代碼將同步執行。然後在該任務上調用ConfigureAwait將不會產生任何影響,因爲不會涉及其他線程。

聽起來好像數據是否在內存中(因爲您正在重複讀取同一文件)file.ReadAsync可以同步完成,因此它會在同一個線程上完成。

相關問題