C# 设置CancellationToken时抛出任意异常是否是一种不良做法?

C# 设置CancellationToken时抛出任意异常是否是一种不良做法?,c#,async-await,C#,Async Await,当检测到CancellationToken.isCancellRequested时,通过抛出除OperationCancelledException以外的内容使我的库中断方法是否是一种不良做法 例如: async任务ConnectAsync(字符串主机、int端口、CancellationToken-ct) { var client=new TcpClient(); 尝试 { 使用(ct.Register(client.Close,true)) { 等待client.ConnectAsync(主

当检测到
CancellationToken.isCancellRequested
时,通过抛出除
OperationCancelledException
以外的内容使我的库中断方法是否是一种不良做法

例如:

async任务ConnectAsync(字符串主机、int端口、CancellationToken-ct)
{
var client=new TcpClient();
尝试
{
使用(ct.Register(client.Close,true))
{
等待client.ConnectAsync(主机、端口);
}
//请在这里挑选打斗者,因为ct.Register()可能已经用软管冲洗了我们的客户
ct.ThrowIfCancellationRequested();
}
捕获(例外)
{
client.Close();
投掷;
}
返回客户;
}
取消时,这可能会引发
ObjectDisposedException
NullReferenceException
,具体取决于时间。(因为同时调用
TcpClient.Close()
时,
TcpClient.ConnectAsync()
可以抛出其中一个。)

现在,我可以这样解决这个问题:

async任务ConnectAsync(字符串主机、int端口、CancellationToken-ct)
{
var client=new TcpClient();
尝试
{
使用(ct.Register(client.Close,true))
{
尝试
{
等待client.ConnectAsync(主机、端口);
}
捕获(例外)
{
//这些例外情况很可能是因为我们关闭了
//与ct.Register()的连接。将它们转换为
//操作取消异常(如果是这种情况)
ct.ThrowIfCancellationRequested();
投掷;
}
}
//请在这里挑选打斗者,因为ct.Register()可能已经用软管冲洗了我们的客户
ct.ThrowIfCancellationRequested();
}
捕获(例外)
{
client.Close();
投掷;
}
返回客户;
}
同样,在调用层次结构的每一层(如适用):

async任务ConnectSslStreamAsync(字符串主机、int端口、CancellationToken ct)
{
var client=await ConnectAsync(主机、端口、ct);
尝试
{
ct.ThrowIfCancellationRequested();
var sslStream=新的sslStream(client.getStream());
使用(ct寄存器(sslStream.Close))
{
尝试
{
等待sslStream.authenticatesclientsync(…);
}
捕获(例外)
{
//这些例外情况很可能是因为我们关闭了
//使用ct.Register()进行流。将它们转换为
//操作取消异常(如果是这种情况)
ct.ThrowIfCancellationRequested();
投掷;
}
}
//在这里找到挣扎者,因为ct.Register()可能已经用水管冲洗了我们的水流
ct.ThrowIfCancellationRequested();
}
捕获(例外)
{
client.Close();
投掷;
}
返回客户;
}
但这增加了这些额外的代码,而实际上,只需要在最外层进行一次检查。(在取消来源。)


让这些任意的异常自由存在是一种不好的做法吗?或者在最外层调用方忽略它们?

如果请求取消,则应抛出
操作取消异常。当代码的使用者遇到异常时,他们将不知道这是取消还是其他原因


也就是说,如果由于注册close delegate而无法抛出
OperationCanceledException
,则可以尝试提供的方法,在该方法中,您将创建一个用于关闭tcpClient或流的任务,并验证哪个任务先完成,然后采取相应的操作

当微软明确地添加了一个抛出异常的方法时,不,“糟糕的做法”并不是首先想到的。仅仅假装它没有被抛出是一种糟糕的做法。我甚至不会将
ct.ThrowIfCancellationRequested()
放在
ConnectAsync
的末尾。如果令牌在调用后立即被取消,但在
ConnectAsync
返回之前被取消,该怎么办?我宁愿让库的客户机处理各种竞速异常,只记录可能的副作用。
ct.ThrowIfCancellationRequested()
之所以存在,是因为没有它,就存在一种微妙的竞速,
使用(ct.Register(…)
可以关闭打开的连接,即使我们的
ConnectAsync()
继续取得成功。在我们的示例中,这可能没有什么不同(调用方可能无论如何都会处理连接),但作为经验法则,该结构可能值得保留,以在我们的方法继续更改状态的情况下保持完整性(原子性);等待之后。它解决了什么问题?@Saravanan您在
ConnectSslStreamAsync()
示例中的意思是什么?在
等待
之后,在更多的工作之前,不是放置它们的最标准的地方之一吗?(因为在一段非同步的间歇之后等待着恢复,而在此期间,世界的许多地方可能已经发生了变化。)我不确定你的第二段是什么意思。您链接到的答案谈到当使用第二个
CancellationTokenSource
进行超时时,将
operationcanceledexception
更改为
TimeoutException
,这可以通过比较异常的
CancellationToken
字段(如接受的答案所示)实现。这里的问题是关于将某个异常X更改为
操作取消异常
,但这似乎并不证明使用
.WithCancellation()
是合理的,因为可以
捕获(异常){ct.throwifccancellationrequest;throw;}
,如上所示。