当检测到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;
}
一旦取消,这具有投掷ObjectDisposedException
或NullReferenceException
取决于定时的可能性。 (因为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;
}
但是这会增加时,在现实中,所有的需要这些额外的代码是在最外层的一个检查。 (在取消源处)
让这些随意的例外飞行是不好的做法吗?还是忽略了传统的最外面的来电者?
当微软明确地添加一个抛出异常的方法时,不,“不好的做法”不是第一个想到的东西。只假装它没有被抛出是一个坏习惯。 –
我甚至不会在'ConnectAsync'的末尾放置'ct.ThrowIfCancellationRequested()'。如果在此调用之后但在ConnectAsync返回之前令牌被取消,该怎么办? 我宁愿让图书馆的客户处理各种赛车异常,只会记录可能的副作用。 – Noseratio
@Noseratio'ct.ThrowIfCancellationRequested()'有,因为没有它,有一个微妙的种族'使用(ct.Register(...))'可以关闭打开的连接,即使我们的ConnectAsync()继续成功。它可能在我们的例子中没有什么不同(并且调用者可能无论如何都会处理连接),但是作为经验法则,该结构可能值得保留,以在我们的方法继续时保持完整性(原子性)改变状态。 – antak