2016-05-31 41 views
5

當檢測到CancellationToken.IsCancelRequested時,通過拋出除OperationCancelledException以外的東西來讓我的程序庫突破方法是否是不好的做法?在設置CancellationToken時拋出任意異常是否是不好的做法?

例如:

async Task<TcpClient> ConnectAsync(string host, int port, CancellationToken ct) 
{ 
    var client = new TcpClient(); 
    try 
    { 
     using (ct.Register(client.Close, true)) 
     { 
      await client.ConnectAsync(host, port); 
     } 

     // Pick up strugglers here because ct.Register() may have hosed our client 
     ct.ThrowIfCancellationRequested(); 
    } 
    catch (Exception) 
    { 
     client.Close(); 
     throw; 
    } 

    return client; 
} 

一旦取消,這具有投擲ObjectDisposedExceptionNullReferenceException取決於定時的可能性。 (因爲TcpClient.ConnectAsync()可以拋出任何一個時TcpClient.Close()同時被調用。)現在

,我可以修復這就像這樣:

async Task<TcpClient> ConnectAsync(string host, int port, CancellationToken ct) 
{ 
    var client = new TcpClient(); 
    try 
    { 
     using (ct.Register(client.Close, true)) 
     { 
      try 
      { 
       await client.ConnectAsync(host, port); 
      } 
      catch (Exception) 
      { 
       // These exceptions are likely because we closed the 
       // connection with ct.Register(). Convert them to 
       // OperationCancelledException if that's the case 
       ct.ThrowIfCancellationRequested(); 
       throw; 
      } 
     } 

     // Pick up strugglers here because ct.Register() may have hosed our client 
     ct.ThrowIfCancellationRequested(); 
    } 
    catch (Exception) 
    { 
     client.Close(); 
     throw; 
    } 

    return client; 
} 

而且同樣在調用層級適用的每一層:

async Task<TcpClient> ConnectSslStreamAsync(string host, int port, CancellationToken ct) 
{ 
    var client = await ConnectAsync(host, port, ct); 
    try 
    { 
     ct.ThrowIfCancellationRequested(); 

     var sslStream = new SslStream(client.getStream()); 
     using (ct.Register(sslStream.Close)) 
     { 
      try 
      { 
       await sslStream.AuthenticateAsClientAsync(...); 
      } 
      catch (Exception) 
      { 
       // These exceptions are likely because we closed the 
       // stream with ct.Register(). Convert them to 
       // OperationCancelledException if that's the case 
       ct.ThrowIfCancellationRequested(); 
       throw; 
      } 
     } 

     // Pick up strugglers here because ct.Register() may have hosed our stream 
     ct.ThrowIfCancellationRequested(); 
    } 
    catch (Exception) 
    { 
     client.Close(); 
     throw; 
    } 

    return client; 
} 

但是這會增加時,在現實中,所有的需要這些額外的代碼是在最外層的一個檢查。 (在取消源處)

讓這些隨意的例外飛行是不好的做法嗎?還是忽略了傳統的最外面的來電者?

+1

當微軟明確地添加一個拋出異常的方法時,不,「不好的做法」不是第一個想到的東西。只假裝它沒有被拋出是一個壞習慣。 –

+1

我甚至不會在'ConnectAsync'的末尾放置'ct.ThrowIfCancellationRequested()'。如果在此調用之後但在ConnectAsync返回之前令牌被取消,該怎麼辦? 我寧願讓圖書館的客戶處理各種賽車異常,只會記錄可能的副作用。 – Noseratio

+0

@Noseratio'ct.ThrowIfCancellationRequested()'有,因爲沒有它,有一個微妙的種族'使用(ct.Register(...))'可以關閉打開的連接,即使我們的ConnectAsync()繼續成功。它可能在我們的例子中沒有什麼不同(並且調用者可能無論如何都會處理連接),但是作爲經驗法則,該結構可能值得保留,以在我們的方法繼續時保持完整性(原子性)改變狀態。 – antak

回答

2

如果要求取消您應該拋出OperationCancelledException。當你的代碼的消費者得到異常時,如果它實際上是取消或其他東西,他們將不知道。

這就是說,如果你不能夠引發OperationCancelledException,因爲登記密切委託的,你可以嘗試的方法提供here在這裏您將創建一個任務關閉的TcpClient或流,並驗證其任務已完成第一,並採取行動因此。

+0

我不確定你的第二段是什麼意思。當你爲超時目的使用第二個'CancellationTokenSource'時,你可以通過比較異常的'CancellationToken'字段(如接受的答案中所示)來解釋將「OperationCancelledException」更改爲'TimeoutException'的答案)。這裏的問題是關於將一​​些異常X改爲OperationCancelledException,但是當只能捕獲異常時,似乎並不需要使用.WithCancellation()。扔; ''如上所示。 – antak

相關問題