问题描述
我有一个 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。
解决方法
使用单个 CancellationTokenSource
并将 令牌 传递给每个方法。无论如何,您应该在任何异常之后清理端口,而不仅仅是取消,所以 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
我不确定操纵取消令牌的最佳方法是什么,但这听起来更像是 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
。
您可以对所有方法使用单个 CancellationTokenSource
。要关闭端口,您可以简单地 register 对取消令牌的回调,该回调将在令牌源被取消时收到通知。
_cancellationTokenSource.Token.Register(() =>
{
if(port.IsOpen)
{
port.Close();
}
});