C#任务取消未按预期工作:需先清除旧任务再启动新任务
解决方案:确保旧任务清理完成后再启动新任务
你的核心问题是没有等待旧任务完成清理就直接启动了新任务——你只触发了旧任务的取消信号,但没等待它执行完关闭WebSocket等清理逻辑,就立刻创建了新任务。要解决这个问题,需要在字典中同时保存旧任务的引用,取消后等待任务完全结束,再启动新任务。
关键修改步骤
- 扩展字典存储内容:不仅保存
CancellationTokenSource,还要保存正在运行的任务实例,这样才能等待它完成。 - 取消后等待旧任务:触发取消信号后,
await旧任务直到它执行完清理逻辑。 - 处理异步任务的Unwrap:因为
Camera.IPCameraService.Main是异步方法,Task.Factory.StartNew会返回Task<Task>,需要用Unwrap()获取实际的异步任务。
修改后的代码
启动摄像头任务的主方法
// 替换原字典,存储选中索引、取消令牌、运行中任务的元组 internal static Dictionary<int, (int SelectedIndex, CancellationTokenSource Cts, Task RunningTask)> cameraTasks = new Dictionary<int, (int, CancellationTokenSource, Task)>(); private async Task IPCameraMethod() { if (string.IsNullOrWhiteSpace(textBoxUrl.Text) || !int.TryParse(SourceComboBox.Name.Replace("comboBox", ""), out int cameraIndex) || comboBoxSavedCameras.SelectedIndex < 0) { return; } string url = textBoxUrl.Text; int selectedItemIndex = comboBoxSavedCameras.SelectedIndex; // 处理旧任务:取消→等待完成→释放资源 if (cameraTasks.TryGetValue(cameraIndex, out var oldEntry)) { if (selectedItemIndex != oldEntry.SelectedIndex) { oldEntry.Cts.Cancel(); try { // 等待旧任务完全执行完清理逻辑 await oldEntry.RunningTask; } catch (OperationCanceledException) { // 任务被取消是预期行为,忽略该异常 } finally { oldEntry.Cts.Dispose(); cameraTasks.Remove(cameraIndex); } Debug.WriteLine("旧任务清理完成"); } } // 启动新任务 var newCts = new CancellationTokenSource(); // Unwrap()将Task<Task>转换为Task,确保等待的是Main方法的异步流程 var newTask = Task.Factory.StartNew(() => Camera.IPCameraService.Main(SourceImageControl, selectedItemIndex, newCts.Token, url), newCts.Token, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default ).Unwrap(); cameraTasks[cameraIndex] = (selectedItemIndex, newCts, newTask); Debug.WriteLine("CREATING NEW TASK"); await newTask; }
优化被调用的任务方法(可选但建议)
为了让旧任务更快响应取消,建议在RunYolo等耗时方法中也检查取消令牌,避免循环迭代卡住:
public async Task Main(System.Windows.Controls.Image imageControl, int cameraIndex, CancellationToken token, string cameraUrl) { CameraURLs[cameraIndex] = cameraUrl; // 启动阶段也检查取消 token.ThrowIfCancellationRequested(); await StartSocket(cameraIndex); token.ThrowIfCancellationRequested(); await StartStream(cameraIndex, cameraUrl); EventHandler(cameraUrl, cameraIndex); while (!token.IsCancellationRequested) { token.ThrowIfCancellationRequested(); // 传递令牌给RunYolo,让内部也能响应取消 var frame = await RunYolo(cameraIndex, token); if (frame != null) await UpdateDisplay(frame, imageControl); } if (token.IsCancellationRequested) { Debug.WriteLine("Clearing Websocket!"); var ws = CameraWebsockets[cameraIndex]; if (ws != null && ws.State == System.Net.WebSockets.WebSocketState.Open) { await ws.CloseAsync(WebSocketCloseStatus.NormalClosure, "Cancellation requested", CancellationToken.None); await Task.Delay(500); } } } // 示例:修改RunYolo方法接受取消令牌 public async Task<Frame> RunYolo(int cameraIndex, CancellationToken token) { token.ThrowIfCancellationRequested(); // 原有逻辑中适时检查token,比如在IO操作前、循环迭代时 // ... }
效果验证
修改后输出顺序会变为:
旧任务清理完成 Clearing Websocket! CREATING NEW TASK
完全符合你先清理再启动新任务的预期。
内容的提问来源于stack exchange,提问作者Captain C
相关产品推荐
相关产品推荐

