C#异步方法中任务完成前无法发送WebSocket消息的问题
问题解决:确保WebSocket停止消息在任务取消前发送成功
核心问题分析
- 取消令牌过早终止发送逻辑:调用
tuple.Item2.Cancel()后,原任务的CancellationToken立即进入取消状态,导致Main方法中最后执行await SendSocket(...)时,SendSocket开头的token.ThrowIfCancellationRequested()直接抛出异常,跳过消息发送逻辑。 - ReceiveSocket取消导致空值异常:
ReceiveSocket被取消后返回null,RunYolo直接将null传入Cv2.ImDecode,触发ArgumentNullException。 - 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
相关产品推荐
相关产品推荐

