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

C#异步方法中任务完成前无法发送WebSocket消息的问题

问题解决:确保WebSocket停止消息在任务取消前发送成功

核心问题分析

  1. 取消令牌过早终止发送逻辑:调用tuple.Item2.Cancel()后,原任务的CancellationToken立即进入取消状态,导致Main方法中最后执行await SendSocket(...)时,SendSocket开头的token.ThrowIfCancellationRequested()直接抛出异常,跳过消息发送逻辑。
  2. ReceiveSocket取消导致空值异常:ReceiveSocket被取消后返回null,RunYolo直接将null传入Cv2.ImDecode,触发ArgumentNullException。
  3. WebSocket连接提前断开:取消令牌触发后,循环中的ReceiveSocket抛出取消异常,间接导致WebSocket连接被释放,后续发送消息时连接已不可用。

具体修复方案

1. 修改Main方法:确保停止消息发送不受原取消令牌影响

在任务取消时,使用独立的CancellationToken保证停止消息发送完成,最后手动清理WebSocket:

public async Task Main(System.Windows.Controls.Image imageControl, int cameraIndex, CancellationToken token, string cameraUrl)
{
    CameraURLs[cameraIndex] = cameraUrl;

    await StartSocket(cameraIndex, token);
    await StartStream(cameraIndex, cameraUrl, token);
    var stopStreamCompletion = new TaskCompletionSource<bool>();
    EventHandler(cameraUrl, cameraIndex, token);
    
    try
    {
        while (!token.IsCancellationRequested)
        {
            var frame = await RunYolo(cameraIndex, token);
            if (frame != null)
                await UpdateDisplay(frame, imageControl, token);
        }
    }
    catch (OperationCanceledException)
    {
        // 捕获取消异常,继续执行停止消息发送
    }

    // 发送停止消息:使用独立令牌+超时保障,避免被原令牌中断
    using var sendCts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
    var Stop_Server = new { command = "Stop_Stream" };
    try
    {
        await SendSocket(cameraIndex, Stop_Server, sendCts.Token);
        Debug.WriteLine("停止消息已发送");
    }
    catch (Exception ex)
    {
        Debug.WriteLine($"发送停止消息失败: {ex.Message}");
    }
    finally
    {
        // 手动关闭并清理WebSocket
        if (CameraWebsockets.TryGetValue(cameraIndex, out var ws))
        {
            if (ws.State == WebSocketState.Open)
            {
                await ws.CloseAsync(WebSocketCloseStatus.NormalClosure, "任务结束", CancellationToken.None);
            }
            ws.Dispose();
            CameraWebsockets.Remove(cameraIndex);
        }
        Debug.WriteLine("WebSocket已清理");
    }
}

2. 修复RunYolo中的空值异常

在调用Cv2.ImDecode前新增空值检查,避免传入null:

private async Task<Mat> RunYolo(int cameraIndex, CancellationToken token)
{
    try
    {
        token.ThrowIfCancellationRequested();

        byte[] imageBytes = await ReceiveSocket(cameraIndex, token);
        
        // 新增空值与长度检查
        if (imageBytes == null || imageBytes.Length == 0)
        {
            Debug.WriteLine("接收到空的图像数据");
            return null;
        }

        Mat frame = Cv2.ImDecode(imageBytes, ImreadModes.Color);
        return frame;
    }
    catch (OperationCanceledException)
    {
        Debug.WriteLine("RunYolo操作被取消");
        return null;
    }
    catch (Exception ex)
    {
        Console.WriteLine($"处理帧时出错: {ex.Message}");
        Console.WriteLine($"详情: {ex}");
        return null;
    }
}

3. 调整SendSocket的取消逻辑

新增重载方法,支持发送停止消息时忽略取消检查:

public async Task SendSocket(int cameraIndex, object message, CancellationToken token, bool ignoreCancellation = false)
{
    try
    {
        if (!ignoreCancellation)
        {
            token.ThrowIfCancellationRequested();
        }

        if (!CameraWebsockets.TryGetValue(cameraIndex, out var ws))
        {
            Debug.WriteLine("WebSocket连接不存在");
            return;
        }

        var json = JsonConvert.SerializeObject(message);
        var bytes = Encoding.UTF8.GetBytes(json);

        await ws.SendAsync(
            new ArraySegment<byte>(bytes), 
            WebSocketMessageType.Text, 
            true, 
            ignoreCancellation ? CancellationToken.None : token
        );
    }
    catch (OperationCanceledException)
    {
        if (!ignoreCancellation)
        {
            Debug.WriteLine("SendSocket操作被取消");
        }
        // 停止消息发送失败时忽略取消异常
    }
    catch (System.Net.WebSockets.WebSocketException ex)
    {
        Console.WriteLine($"WebSocket异常: {ex.Message}");
    }
}

在Main方法中调用该重载:

await SendSocket(cameraIndex, Stop_Server, CancellationToken.None, ignoreCancellation: true);

4. 优化IPCameraMethod中的任务等待逻辑

确保原任务完全执行完清理逻辑后,再创建新任务:

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 (tokenSources.TryGetValue(cameraIndex, out var tuple))
    {
        if (selectedItemIndex != tuple.Item1)
        {
            tuple.Item2.Cancel();
            
            try
            {
                // 等待原任务完全完成(包括停止消息发送和WebSocket清理)
                await tuple.Item3;
            }
            catch (OperationCanceledException)
            {
                // 预期的取消异常,无需处理
            }
            finally
            {
                tuple.Item2.Dispose();
                tokenSources.Remove(cameraIndex);
            }
        }
        else
        {
            return;
        }
    }

    var newCts = new CancellationTokenSource();
    var newTask = Camera.IPCameraService.Main(SourceImageControl, selectedItemIndex, newCts.Token, url);

    Debug.WriteLine("新任务已创建");
    tokenSources[cameraIndex] = Tuple.Create(selectedItemIndex, newCts, newTask);
}

关键修复点总结

  • 发送停止消息时使用独立CancellationToken,避免被原任务的取消令牌中断。
  • 增加空值检查,防止Cv2.ImDecode传入null引发异常。
  • 确保原任务完全执行完清理逻辑后,再创建新任务。
  • 手动管理WebSocket的关闭和释放,避免提前断开。

内容的提问来源于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.11 21:30:52