You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#任务取消未按预期工作:需先清除旧任务再启动新任务

解决方案:确保旧任务清理完成后再启动新任务

你的核心问题是没有等待旧任务完成清理就直接启动了新任务——你只触发了旧任务的取消信号,但没等待它执行完关闭WebSocket等清理逻辑,就立刻创建了新任务。要解决这个问题,需要在字典中同时保存旧任务的引用,取消后等待任务完全结束,再启动新任务。

关键修改步骤

  1. 扩展字典存储内容:不仅保存CancellationTokenSource,还要保存正在运行的任务实例,这样才能等待它完成。
  2. 取消后等待旧任务:触发取消信号后,await旧任务直到它执行完清理逻辑。
  3. 处理异步任务的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 06:33:18