2016-05-31 95 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

相关问题