在这种情况下,单个 CancellationTokenSource 如何适用于所有方法?
Posted
技术标签:
【中文标题】在这种情况下,单个 CancellationTokenSource 如何适用于所有方法?【英文标题】:How can a single CancellationTokenSource work for all methods in this scenario? 【发布时间】:2021-06-25 06:44:22 【问题描述】:我有一个 GUI 应用程序,其中断开按钮需要取消异步任务并关闭端口。 到目前为止,我可以通过在每种方法中使用 CancellationTokenSource 来实现。端口只应在任务取消后关闭,因此首先取消令牌,然后在方法中捕获 OperationCanceledException,进而关闭端口。
我在开头声明:
private CancellationTokenSource my_cancelationTokenSource;
private CancellationTokenSource my_cancelationTokenSource_2;
private CancellationTokenSource my_cancelationTokenSource_3;
那么作为一个例子我有以下三个方法MyMethodAsync、MySecondMethodAsync和MyThirdMethodAsync:
private async Task MyMethodAsync(port)
byte[] sent;
my_cancelationTokenSource = new CancellationTokenSource();
try
do
await Task.Delay(period, my_cancelationTokenSource.Token);
//some code here..
byte[] received = await message.SendReceive(sent, my_cancelationTokenSource.Token);
//some code here..
while (true);
catch (OperationCanceledException)
try
my_cancelationTokenSource.Dispose();
my_cancelationTokenSource = null;
if (port.IsOpen)
port.Close();
catch
private async Task MySecondMethodAsync(byte[] set)
my_cancelationTokenSource_2 = new CancellationTokenSource();
try
await Task.Delay(period, my_cancelationTokenSource_2.Token);
//some code here
bool check = await MyThirdMethod(set);
//some code here
catch (OperationCanceledException)
try
my_cancelationTokenSource_2.Dispose();
my_cancelationTokenSource_2 = null;
if (port.IsOpen)
port.Close();
catch
private async Task MyThirdMethodAsync(byte[] set)
my_cancelationTokenSource_3 = new CancellationTokenSource();
try
await Task.Delay(period, my_cancelationTokenSource_3.Token);
//some code here
bool check = await MyThirdMethod(set);
//some code here
catch (OperationCanceledException)
try
my_cancelationTokenSource_3.Dispose();
my_cancelationTokenSource_3 = null;
if (port.IsOpen)
port.Close();
catch
这里是断开按钮事件:
private void Button_Disconnect_Click(object sender, RoutedEventArgs e)
my_cancelationTokenSource?.Cancel();
my_cancelationTokenSource_2?.Cancel();
my_cancelationTokenSource_3?.Cancel();
我将拥有数十个这样的方法,并且通过这种方式,我必须为每个方法声明 CancellationTokenSource。如何修改代码以使所有方法的一个取消令牌都适用于这种情况?只需为所有方法声明和使用 my_cancelationTokenSource。
【问题讨论】:
那么,使用单个 CTS 吗?您可能希望在连接形成(并且断开连接按钮变为活动状态)时实例化它,并且您希望避免在每个方法中处理它(甚至可能将其设置为 null) 我无法通过使用单个 cancelationTokenSource 来实现它。 您并没有真正解释为什么单个 CTS 对您不起作用,很难评论什么是好的解决方案。然而,作为一个在黑暗中的镜头,您有能力创建链接的 CTS(因此您可以拥有 1 个主控,该主控将取消所有子级以及为每个单独的方法保留一个子级,保留取消特定方法而不取消其余方法的功能)。看看CancellationTokenSource.CreateLinkedTokenSource
(docs.microsoft.com/en-us/dotnet/api/…)
@pnatk 我们很难帮助解决“无法管理”的问题。您能否发布您尝试过的代码,以及它不起作用的原因?
@Knoop 我的问题是我不知道如何正确实现它。这是一个我有点害怕的地方。
【参考方案1】:
使用单个 CancellationTokenSource
并将 token 传递给每个方法。无论如何,您应该在任何异常之后清理端口,而不仅仅是取消,所以 finally 就可以了。
您首先需要处理/忽略OperationCancelledException
调用这些方法的任何内容。 (或者如果调用者不需要知道取消,你可以在函数内吃掉它——如果不知道它们是如何被调用的,很难给出建议)
private async Task MyMethodAsync(port, CancellationToken cancelToken)
byte[] sent;
try
do
await Task.Delay(period, cancelToken);
//some code here..
byte[] received = await message.SendReceive(sent, cancelToken);
//some code here..
while (true);
finally
try
if (port.IsOpen)
port.Close();
catch
private void Button_Disconnect_Click(object sender, RoutedEventArgs e)
my_cancelationTokenSource.Cancel();
my_cancelationTokenSource.Dispose();
【讨论】:
在这种情况下,我需要在一开始就这样声明为全局变量?:CancellationTokenSource my_cancelationTokenSource = new CancellationTokenSource(); 是的,如果它只在关机时取消,但我认为每次连接时创建它是否有意义? 我明白你的意思是声明为全局变量但在连接时创建? 您也没有处理和清空 TokenSource。是故意的吗? 是的,成员变量(不是全局变量),在需要时创建,但是在不知道您的应用程序流程的情况下很难给出建议是的,您应该处理源代码,我的错误在那里。如果您每次需要它时都重新创建它,则不需要归零。【参考方案2】:我会有几十个这样的方法,通过这种方式我必须为每个方法声明 CancellationTokenSource
我不太确定操作取消令牌的最佳方法是什么,但这听起来更像是 OOP 问题。您可以使用类来重用共享逻辑,而不是让多个方法共享相同的逻辑。例如,您可以有一个基类声明CancellationTokenSource
的某些受保护字段、将使用该字段的公共方法和将执行特定工作的抽象方法。
public abstract class MyAsyncWorkerBase
protected CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
public void Cancel()
_cancellationTokenSource.Cancel();
public async Task DoWork(IDontKnowTheType port)
try
await DoMySpecificWork(port);
catch (OperationCanceledException)
try
_cancellationTokenSource.Dispose();
_cancellationTokenSource = null;
if (port.IsOpen)
port.Close();
catch
protected abstract Task DoMySpecificWork(IDontKnowTheType port);
然后你可以继承你的基类,然后在DoMySpecificWork
中做你的事情。
我不知道你的代码库所以我的例子有点模糊,你当然可以改进它。您还可以添加一些代码来处理多次调用DoWork
。
最后,为方便起见,您可以将所有 MyAsyncWorkerBase
放入某个数组中,然后在 Button_Disconnect_Click
中循环遍历它们并在每个数组上调用 Cancel
。
【讨论】:
谢谢。您能否还添加在方法中调用/使用它的方式? 抱歉,不确定你想在方法中调用什么。如果你问如何覆盖DoMySpecificWork
应该如何工作 - 你只需将你的代码放在那里并将 _cancellationTokenSource
传递到你需要它的地方(例如message.SendReceive
)如果你问如何使用类本身 - 很难说因为我不知道如何其余代码看起来像 =) 最简单的方法 - 您可以在开始时创建所有实例,然后在按钮处理程序中调用它们(我假设,因为它的 GUI 应用程序)就像 await myWorker.DoWork(port)
我的意思是您可以使用我的一种方法 MySecondMethod 作为示例来展示它是如何在那里使用的。你的方式很有趣,但我只需要确定实施。【参考方案3】:
您可以对所有方法使用单个 CancellationTokenSource
。要关闭端口,您可以简单地 register 取消令牌上的回调,当令牌源被取消时将收到通知。
_cancellationTokenSource.Token.Register(() =>
if(port.IsOpen)
port.Close();
);
【讨论】:
如果 (port.IsOpen) port.Close(); ?您能否更明确地添加它的位置? 你没有指定port
的范围,我想它是一个类上的一个字段,所以它将替换每个方法上的代码。每当取消令牌源被取消时,它都会关闭端口,因此您可以将此代码放在创建端口的位置(如果您已经可以访问取消令牌)或创建取消令牌源时。
您忘记注销了。这段代码是内存不足异常的潜在坑。它还需要 trycatch 块,因为未处理的异常会使您的应用死机。
@eocron 注册回调由取消令牌源保存,并在取消或处置 cts 后清理。风险在于回调指向比 cts 寿命更长的引用并且 cts 未被清除。关于异常,我们确实可以包装一个try/catch,因为当cts被取消时,回调会同步执行,但我认为这不是不使用它的理由,事件处理程序也是如此。可以使用应用级别的全局处理程序。
我认为,您应该在回答中指出这一点。以上是关于在这种情况下,单个 CancellationTokenSource 如何适用于所有方法?的主要内容,如果未能解决你的问题,请参考以下文章