新的異步ExecuteReaderAsync
需要一個CancellationToken。有什麼辦法取消舊的同步ExecuteReader
?是否可以在ExecuteReader中使用CancellationToken?
在我們的例子中,所有的數據操作在後臺線程上是同步的,所以await
不是一個選項。我不想開始第二個線程 - Task.Run(() => command.ExecuteReaderAsync(token)).Result
似乎是一個浪費,只是爲了能夠從UI線程中取消。
新的異步ExecuteReaderAsync
需要一個CancellationToken。有什麼辦法取消舊的同步ExecuteReader
?是否可以在ExecuteReader中使用CancellationToken?
在我們的例子中,所有的數據操作在後臺線程上是同步的,所以await
不是一個選項。我不想開始第二個線程 - Task.Run(() => command.ExecuteReaderAsync(token)).Result
似乎是一個浪費,只是爲了能夠從UI線程中取消。
與使用Begin或Async API及其線程池連續性相比,性能測試顯示使用專用同步數據讀取線程的性能測試幾乎有兩倍的性能優勢。 (由於幾千萬行會在儘可能多秒被加載,我們在這種情況下更喜歡性能。)
方便令牌傳遞擴展方法:
public static SqlDataReader ExecuteReader(this SqlCommand command, CommandBehavior commandBehavior, CancellationToken cancel)
{
try
{
using (cancel.Register(command.Cancel))
return command.ExecuteReader(commandBehavior);
}
catch (SqlException)
{
cancel.ThrowIfCancellationRequested();
throw;
}
}
我做了一些洞穴探險與反射反編譯。 Begin和Async版本都非常節儉,但都完全基於TPL異步。因爲這個原因,兩個線程池都有線程池調度。
此擴展方法沒有線程開銷。在令牌源上調用Cancel
的線程也將調用command.Cancel
,這將立即在數據線程中導致SqlException
。
我不能迴應權威性您的修改,但有你應該知道你最初的問題幾件事情:
Task.Run(...).Result
是阻塞;該語法有點誤導。await Task.Run(() => command.ExecuteReaderAsync(token));
將僅阻止執行方法的其餘部分;允許其餘的被視爲回調。await Task.Run(() => command.ExecuteReaderAsync(token), token);
按上述方式工作,但允許任務並行庫兌現您的取消令牌。至於主要問題,this msdn article建議ExecuteReaderAsync()是真正遵守這個cancellationToken。請記住,框架中有幾個方法不會實際執行此操作。
是的,'.Result'就像'ExecuteReader'一樣被阻塞。同步的'ExecuteReader'是我更喜歡使用的。這一切都在後臺線程上,不需要異步延續調度,所以'await'不存在問題。 – jnm2
鑑於你想堅持使用同步代碼,爲什麼不用'ExecuteReaderAsync(token,...)替換ExecuteReader(...)。你不必用'Task.Run'來包裝它。 – Noseratio
@Noseratio Duh,我習慣於在UI線程上導致死鎖,但是在後臺線程的情況下,ExecuteReaderAsync(...)。Result'運行的很好。繼續,它是否阻塞並使用線程池線程來處理繼續操作?我認爲我的擴展方法有一直保持在一個線程上的好處。 – jnm2
是的,'ExecuteReaderAsync(...)。Result'將阻止任何被調用的線程。但它並不一定意味着它會阻止更多的線程,因爲它可能會在場景後面使用異步IO(我不確定這一點,但這是合乎邏輯的)。無論如何,如果您擔心應用程序的可伸縮性,請使用'async/await'並且不要冗餘地阻塞線程。 – Noseratio