当前位置: 首页 > 科技观察

Dotnet线程取消深度

时间:2023-03-14 11:02:04 科技观察

取消的概念通常是我们最熟悉的,就是一个方法的挂起。暂停完成。当一个方法终止时,该方法将不再继续执行,该方法之前完成的部分将被丢弃,并返回一个集合结果。取消是不同的。通常,取消是其他代码发出的命令,即一部分代码请求取消,另一部分代码响应取消。而且,实际发生的是请求代码只是通知响应代码,希望它停止执行;它可以停止的点,甚至可能完全忽略取消请求。概念清楚了,怎么做呢?由于取消令牌是一方的请求,另一方响应,因此响应代码能够知道并响应取消请求很重要。在Dotnet中,有一种东西被称为CancellationTokens。此令牌是取消请求的载体。当请求代码发起取消时,实际上是对“取消令牌”发起取消操作,然后响应代码会正确响应取消令牌。如果您在这里看到一些混淆,请查看示例代码:asyncTaskSomethingAsync(intdata,CancellationTokencancellationToken){v??arresult=awaitFirstStepAsync(data,cancellationToken);awaitSecondStepAsync(intermediateValue,cancellationToken);}响应码基本就是这个样子这里的CancellationToken就是上面提到的取消令牌。CancellationToken可以在任何地方设置取消:用户按下取消按钮,或者客户端断开连接、超时等。重要的是,当设置为取消时,表明响应代码需要处理取消。注意:一个CancellationToken只能被取消一次。一旦取消,它将永远保持取消状态。带有取消标记的方法定义上面的示例是带有取消标记的典型方法定义。按照微软的习惯,带有CancellationToken的方法有如下约定:CancellationToken通常是最后一个参数。方法通常提供重载或默认参数值,以便调用者可以直接调用而无需提供取消令牌。当然,这是一个不可执行的协议。如果您不介意别人看起来尴尬,您可以忽略此协议。看几个例子:TaskSomethingAsync(intdata)=>SomethingAsync(data,CancellationToken.None);asyncTaskSomethingAsync(intdata,CancellationTokencancellationToken){...}asyncTaskSomethingAsync(intdata,CancellationTokencancellationToken=default){...}在这里,CancellationToken代表任何类型的取消。通过CancellationToken参数,该方法声明它可以响应取消。实际上,这只是一个声明。在代码中,CancellationToken可能会被忽略。因此,有这个声明只是表示该方法可能支持取消,而不是必须的。方法对取消的响应上面说过,响应码可以响应取消也可以不响应。即使响应代码确实响应取消,通常也会有不同的情况。一般来说,如果取消请求到达时响应方法实际上取消了一些工作,则会抛出一个OperationCanceledException来通知调用者;如果取消被忽略,或者取消请求来得太晚,任务已经完成,响应方式将是正常的。返回而不抛出OperationCanceledException。这在Microsoft的基本类库(BCL)中很明显。在大多数情况下,异常是逐层传播的。再看看上面的例子:asyncTaskSomethingAsync(intdata,CancellationTokencancellationToken){v??arresult=awaitFirstStepAsync(data,cancellationToken);awaitSecondStepAsync(intermediateValue,cancellationToken);}如果FirstStepAsync或SecondStepAsync也抛出异常,则ExpOperationC将从SomethingAsync传递给调用方。这里要强调一下:我看过很多代码,请求取消时会直接返回,不会抛出异常。不要这样做。调用者不知道取消是被接受还是被忽略,这会导致大问题。在代码审查期间多次看到一个常见的错误用法:asyncTaskSomethingAsync(CancellationTokencancellationToken){v??artest=awaitTask.Run(()=>{...},cancellationToken);...}//注意这个例子的写法是错误的。这个需要特别说明。很多人将delegation和CancellationToken传递给Task,期望在token取消时取消delegation。请注意,这种理解是错误的。Task.Run是对线程池的委托调度,是立即完成的瞬时动作。这里CancellationToken的作用是取消调度动作,这个动作立即完成。换句话说,一旦到达这条线,调度操作将立即完成,取消令牌没有用,将被忽略。因此,这种情况下,就没有必要使用CancellationToken了,应该写成下面这样:);...}这样写,才是正确的表达方式,表示委托本身需要响应令牌。这是一个容易出错的知识点,记住了。