當檢測到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