使用Task.Run创建重复任务时,如何取消旧任务并确保单任务运行?
问题:创建新任务时取消旧的长期运行任务
我写了如下代码,用Task.Run执行一个长期运行的while循环:
public void Test(){ var task1 = Task.Run(() => { while(true){ // 无限循环 } } }
调用Test()时,task1会执行,要是立刻再次调用Test(),就会同时存在两个任务。请问有没有办法在创建新任务时取消已在运行的旧任务?
参考Michał Turczyn的回复后,我修改了代码:
int i = 0; private CancellationTokenSource cancellationTokenSource; public void Test(int b) { if (cancellationTokenSource != null) { // 取消之前创建的任务 cancellationTokenSource.Cancel(); } cancellationTokenSource = new CancellationTokenSource(); var task1 = Task.Run(() => { while (!cancellationTokenSource.IsCancellationRequested) { Console.WriteLine(b); // 无限循环 } }); } public void button_pushed(){ Console.WriteLine("pushed"); Test(i); i++; return; }
日志输出如下:
pushed 0 0 0 0 0 pushed 1 1 1 1 pushed 1 1 1 1 2 2 2 2 2 1 1 1 1 2 2 2 pushed 1 1 1 1 2 2 2 2 2 1 1 1 1 3 3 3 3 2 2 2 ....
看起来旧任务还在运行,但第三次执行后,最初的输出(0)消失了,这是时序问题吗?我期望的输出效果是:
pushed 0 0 0 0 pushed 1 1 1 1 pushed 2 2 2 2 ....
问题根源
- 闭包导致取消信号失效:旧任务捕获的是
cancellationTokenSource变量的引用,当你创建新任务时把这个变量指向了新的实例,旧任务里的取消判断实际上在检查新令牌的状态,根本没收到原本的取消信号,所以会继续运行。 - 未等待旧任务终止:调用
Cancel()后立刻启动新任务,旧任务不会马上停止,就算取消信号正常,它也得等到下一次循环检查时才会退出,更别说现在信号还失效了。
修复方案
给每个任务绑定独立的取消令牌,同时等待旧任务完全终止后再启动新任务:
int i = 0; private CancellationTokenSource _previousCts; private Task _runningTask; public async void Test(int b) { // 处理旧任务:取消+等待终止+释放资源 if (_previousCts != null) { _previousCts.Cancel(); try { if (_runningTask != null) await _runningTask; } catch (OperationCanceledException) { // 任务被取消属于预期情况,无需额外处理 } finally { _previousCts.Dispose(); _previousCts = null; } } // 创建新的取消令牌和任务 _previousCts = new CancellationTokenSource(); var token = _previousCts.Token; _runningTask = Task.Run(() => { while (!token.IsCancellationRequested) { Console.WriteLine(b); // 可选:加短延迟,既避免输出过于密集,也让取消检查更及时 // Thread.Sleep(100); } }, token); } public void button_pushed() { Console.WriteLine("pushed"); Test(i); i++; }
修复说明
- 绑定独立令牌:每个任务捕获的是当前的
token实例,后续修改_previousCts不会影响旧任务的取消判断,确保旧任务能收到正确的取消信号。 - 等待旧任务终止:用
await _runningTask确保旧任务完全退出后再启动新任务,彻底避免多任务同时输出的情况。 - 资源释放:调用
Dispose()释放旧的CancellationTokenSource,避免资源泄漏。
修改后每次点击按钮,旧任务会被正确取消并终止,新任务才会启动,输出就能符合你的预期。
内容的提问来源于stack exchange,提问作者yolo
相关产品推荐
相关产品推荐

