【问题标题】:Is it bad practice to throw an arbitrary exception when CancellationToken is set?设置 CancellationToken 时抛出任意异常是不好的做法吗?
【发布时间】:2016-05-31 10:27:15
【问题描述】:

当检测到CancellationToken.IsCancelRequested 时,通过抛出OperationCancelledException 以外的其他内容让我的库打破方法是不好的做法吗?

例如:

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;
}

但这会添加这些额外的代码,而实际上,只需要在最外层进行一次检查即可。 (在取消源处。)

让这些任意异常发生是不好的做法吗?还是在最外层的调用者常规忽略它们?

【问题讨论】:

  • 当微软显式添加一个引发异常的方法时,不,“坏习惯”并不是首先想到的。只是假装它没有被抛出是一种不好的做法。
  • 我什至不会把ct.ThrowIfCancellationRequested() 放在ConnectAsync 的末尾。如果令牌在此调用之后但在 ConnectAsync 返回之前被取消怎么办?我宁愿让图书馆的客户处理各种赛车异常,只记录可能的副作用。
  • @Noseratio ct.ThrowIfCancellationRequested() 之所以存在,是因为没有它,using (ct.Register(...)) 可以关闭打开的连接,即使我们的 ConnectAsync() 继续成功。在我们的示例中它可能没有什么不同(调用者可能无论如何都会处理连接),但作为经验法则,该结构可能值得保留,以在我们的方法继续执行的情况下保持完整性(原子性)改变状态。
  • 为什么要调用 ct.ThrowIfCancellationRequested();等待之后。它解决了什么目的?
  • @Saravanan 你是说ConnectSslStreamAsync() 的例子吗?在await 之后,在更多工作之前,不是最标准的放置它们的地方之一吗? (因为在异步中断之后等待恢复,而世界大部分地区可能在此期间发生了变化。)

标签: c# async-await


【解决方案1】:

如果请求取消,您应该抛出 OperationCancelledException。当您的代码的使用者遇到异常时,他们将不知道这实际上是取消还是其他原因。

也就是说,如果您因为注册关闭委托而无法抛出OperationCancelledException,您可以尝试here 提供的方法,您将创建一个用于关闭 tcpClient 或流的任务并验证哪个任务首先完成并采取相应措施。

【讨论】:

  • 我不确定你的第二段是什么意思。您链接到的答案谈到将OperationCancelledException 更改为TimeoutException,当一个人使用第二个CancellationTokenSource 来超时时,这可以通过比较异常的CancellationToken 字段来实现(如接受的答案所示)。这里的问题是关于将一​​些异常 X 更改为 OperationCancelledException,但这似乎并不能证明使用 .WithCancellation() 是合理的,因为可以只使用 catch (Exception) { ct.ThrowIfCancellationRequested; throw; },如上所示。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-11-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多