2017-02-17 62 views
0

如果您不輸入任何輸入,爲什麼下面的代碼不會完成?爲什麼即使在取消令牌被取消後,它仍然響應按下的鍵?如何取消Stream.ReadAsync?

// Set up a cancellation token 
var cancellationSource = new CancellationTokenSource(); 

// Cancel the cancellation token after a little bit of time 
Task.Run(async() => 
{ 
    await Task.Delay(TimeSpan.FromSeconds(2)); 
    cancellationSource.Cancel(); 
    Console.WriteLine("Canceled the cancellation token"); 
}); 

// Wait for user input, or the cancellation token 
Task.Run(async() => 
{ 
    try 
    { 
     using (var input = Console.OpenStandardInput()) 
     { 
      var buffer = new byte[1]; 
      Console.WriteLine("Waiting for input"); 
      await input.ReadAsync(buffer, 0, 1, cancellationSource.Token); // This is impossible to cancel??? 
      Console.WriteLine("Done waiting for input"); // This never happens until you press a key, regardless of the cancellation token 
     } 
    } 
    catch (Exception e) 
    { 
     Console.WriteLine(e.Message); // No errors 
    } 
}) 
.Wait(); // Block until complete 

The documentation for Stream.ReadAsync says

如果在完成前取消操作,返回的任務包含狀態屬性的取消值。

這意味着取消取消令牌將取消操作,對吧?然而,出於某種原因the source code for Stream.ReadAsync不會做與取消標記任何東西,如果它沒有事先取消:

public virtual Task<int> ReadAsync(Byte[] buffer, int offset, int count, CancellationToken cancellationToken) 
{ 
    // If cancellation was requested, bail early with an already completed task. 
    // Otherwise, return a task that represents the Begin/End methods. 
    return cancellationToken.IsCancellationRequested 
       ? Task.FromCancellation<int>(cancellationToken) 
       : BeginEndReadAsync(buffer, offset, count); 
} 

因此取消標記參數是沒有意義的 - 我怎樣才能取消異步讀?

+0

請注意,Console.OpenStandardInput正在返回[__ConsoleStream](https://referencesource.microsoft.com/#mscorlib/system/io/__consolestream.cs,de9f3a925342686c)的一個實例,該實例不會覆蓋.ReadAsync –

+0

下次在一段時間後使用適當的構造函數做一些不必要的魔術來取消令牌:'var cancellationSource = new CancellationTokenSource(TimeSpan.FromSeconds(2));'。 https://msdn.microsoft.com/en-us/library/hh139229(v=vs.110).aspx ' –

+0

@PeterBons對我來說,我的方式更可讀地傳達一些事情將被打印輸出到控制檯後 –

回答

0

在控制檯輸入的特殊情況下,似乎比輪詢Console.KeyAvailable財產沒有其他辦法:

var buffer = new byte[1]; 
Console.WriteLine("Waiting for input"); 

while (!Console.KeyAvailable && !cancellationSource.Token.IsCancellationRequested) 
    await Task.Delay(10); // You can add the cancellation token as a second parameter here, but then canceling it will cause .Delay to throw an exception 

if (cancellationSource.Token.IsCancellationRequested) 
{ 
    Console.WriteLine("Canceled; no longer waiting for input"); 
} 
else 
{ 
    await input.ReadAsync(buffer, 0, 1); 
    Console.WriteLine("Got user input"); 
} 

對我來說,這意味着你不能可靠地在一般的方式使用Stream.ReadAsync ,因爲你必須根據你正在處理的Stream的哪個實現來做不同的事情。

編輯:

想到這裏多一點,這會讓你無法取消ReadAsync,因爲Stream抽象類沒有處理異步操作的任何抽象方法的意義;所有你必須做的實現一個Stream是實現一些獲得者和一些阻止方法,這是所有微軟已經完成與__ConsoleStream類。

由於可以保證在Stream上存在的唯一方法是阻塞方法,並且由於不可能取消阻塞調用(您甚至無法在另一個線程上執行阻塞IO操作,所以取消該線程,並且有操作停止),不可能有可取消的異步操作。

因此,Microsoft應該已經刪除了取消令牌參數,或者應該將抽象異步可取消方法放入Stream類中,以便讓__ConsoleStream的人會被迫實施它們。