非阻塞 C# 任务取消





5.00/5 (6投票s)
非阻塞 C# 任务取消。
在我们之前的代码片段 使用 C# 中的 CancellationTokens 取消任务中的循环 中,我尝试解释如何跳出循环的 C# 任务,但是这种情况可能会出现问题。如果我们要等待该任务的任何结果,我们将阻塞调用线程,直到任务返回,如果在主线程上,这不是好事。我们会锁定我们的 UI,并可能导致应用程序崩溃。
因此,我一直在测试不同的方法来跳出该循环而不会引起任何问题,并且您可以通过许多不同的方式实现我们想要的目标。
首先,我认为我可以正确地假设只有在任务将返回某些内容时才需要等待任务完成。如果没有返回值,我们为什么要调用 wait 呢?我们可以直接跳出它,如果我错了请纠正我。如果有一个返回值,那么有必要用 try
/catch
包裹 task 上的 wait 调用来接收其结果。但是,我们可以使用一个 continuation task 来避免此处的锁定,该任务将在第一个任务完成后创建并启动该任务,从而为我们提供来自先前任务的结果以供使用。
如果万一我们要等待任何结果,那么我们必须使用 wait
方法,锁定调用线程。这将导致同样的问题,阻塞该线程,如果此线程是持有我们 UI 的主线程,这对用户体验不利,并可能导致应用程序崩溃。
第一个解决方案,纠正这个问题。
创建一个任务,创建工作任务,并等待它完成。这样,最终您将有两个任务,并且不会锁定 UI 线程。但是,假设您有一个 UI 按钮来启动这些链式任务。您将创建与您单击该按钮一样多的任务。除非您实现一种方法,使其在任何时候只有一个任务在运行。您可以通过发送取消令牌来做到这一点,您可以先杀死该任务,然后再重新创建一个新任务。但这可能会循环回到我们的阻塞问题。
另一种方法是在方法上使用 Async
/Await
。
对于不返回任何值的方法,我们只需要更改我们的代码,以便在请求取消时跳出循环,而不是调用 ThrowIfCancelledRequired()
,我们只需检查是否已请求取消,然后跳出。由于我们没有等待任何结果,我们只需让任务完成。使用此想法,我们甚至可以消除对取消令牌的需求。我们可以使用一个 volatile bool 变量来控制我们的取消。但我发现使用取消令牌更符合“良好实践”。我不太清楚 volatile 访问在多个任务之间的性能如何,所以我保留了取消令牌。这是一个示例代码
CancellationTokenSource _cts;
volatile bool _run = true;
public void Start( )
{
// do something
_cts = new CancellationTokenSource();
var token = _cts.Token;
var t = Task.Factory.StartNew(() =>
{
Console.WriteLine("Start");
while (true)
{
if (token.IsCancellationRequested)
{
Console.WriteLine("Break");
break;
}
Console.WriteLine("."); // just to show progress from task.
Thread.Sleep(1000);
}
}, token);
}
public void Cancel( )
{
Console.WriteLine("Cancel");
//_run = false;
// stop that.
if( _cts != null )
_cts.Cancel();
}
void Main( )
{
Start( );
Console.ReadLine();
Cancel();
}
在 4 秒后按 Enter 键后,这将输出类似以下内容:
Start
.
.
.
.
Cancel
Break
请注意,_cts
是调用 Start
的类的成员变量,因此可以在 Cancel
方法中访问它。回到我们的第一个解决方案,如果我们要声明一个 volatile bool _run
变量(如 Cancel
方法上的注释),我们首先需要取消注释该行以将值更改为 false
,并更改 Start
方法 while
循环中的 true
。
请记住,我仅使用 break
跳出循环,因为该方法不向调用线程返回任何值。否则,我必须 try
/catch
该值。
我希望你喜欢这个,如果你今天学到了一些东西,我的工作就完成了。请留下评论或建议。