{"record":{"id":"2b75b76d788c40e4","repo":"JeffreySu/WeiXinMPSDK","slug":"smartrobotwebsocketclient","errorCode":null,"errorMessage":"智能机器人长连接尚未建立。 ","messagePattern":"智能机器人长连接尚未建立。 ","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Senparc.Weixin.Work/Senparc.Weixin.Work/AdvancedAPIs/SmartRobot/SmartRobotWebSocketClient.cs","lineNumber":227,"sourceCode":"        /// </summary>\n        /// <param name=\"requestId\">智能机器人 WebSocket 请求 ID。</param>\n        /// <param name=\"cancellationToken\">取消令牌。</param>\n        /// <returns>表示异步操作的任务。</returns>\n        public Task SendPingAsync(string requestId = null, CancellationToken cancellationToken = default)\n            => SendCommandAsync(\"ping\", requestId ?? CreateRequestId(), null, cancellationToken);\n\n        /// <summary>发送官方协议中的任意命令，可用于媒体分片上传等扩展命令。</summary>\n        /// <param name=\"command\">智能机器人 WebSocket 命令。</param>\n        /// <param name=\"requestId\">智能机器人 WebSocket 请求 ID。</param>\n        /// <param name=\"body\">命令正文；无正文的命令可传 <see langword=\"null\"/>。</param>\n        /// <param name=\"cancellationToken\">取消令牌。</param>\n        /// <returns>表示异步操作的任务。</returns>\n        public async Task SendCommandAsync(string command, string requestId, object body,\n            CancellationToken cancellationToken = default)\n        {\n            if (_socket.State != WebSocketState.Open)\n            {\n                throw new InvalidOperationException(\"智能机器人长连接尚未建立。 \");\n            }\n\n            var envelope = new SmartRobotSocketEnvelope\n            {\n                cmd = command,\n                headers = new SmartRobotSocketHeaders { req_id = requestId },\n                body = body\n            };\n            var bytes = Encoding.UTF8.GetBytes(envelope.ToJson());\n\n            await _sendLock.WaitAsync(cancellationToken).ConfigureAwait(false);\n            try\n            {\n                await _socket.SendAsync(new ArraySegment<byte>(bytes), WebSocketMessageType.Text, true,\n                    cancellationToken).ConfigureAwait(false);\n            }\n            finally\n            {","sourceCodeStart":209,"sourceCodeEnd":245,"githubUrl":"https://github.com/JeffreySu/WeiXinMPSDK/blob/be573f6f94bdbf718dd5f6cdecb137fbc7ff651e/src/Senparc.Weixin.Work/Senparc.Weixin.Work/AdvancedAPIs/SmartRobot/SmartRobotWebSocketClient.cs#L209-L245","documentation":"SmartRobotWebSocketClient.SendCommandAsync throws this InvalidOperationException when the underlying ClientWebSocket's State is not Open. All public send helpers (SendPingAsync, SendMessageAsync, RespondAsync, RespondWelcomeAsync, UpdateResponseAsync) route through SendCommandAsync, so any command sent before ConnectAndSubscribeAsync completes or after the connection dropped fails. The library requires an established long-lived WebSocket connection before any frame is sent.","triggerScenarios":"Calling SendPingAsync/SendMessageAsync/RespondAsync/RespondWelcomeAsync/UpdateResponseAsync/SendCommandAsync when _socket.State != WebSocketState.Open: before calling ConnectAndSubscribeAsync, before the connect handshake finishes, after the server closed the socket, or after a network drop.","commonSituations":"Fire-and-forget message sends in a bot service that never awaited the connect task; long-running bot processes where the WebSocket silently dropped; racing RespondAsync from a message callback before the subscribe loop opened; reusing a client instance whose connection died and not reconnecting.","solutions":["Call and await ConnectAndSubscribeAsync once before sending any commands; keep the returned task/loop alive.","Check client socket state (or expose a property) before sending: only send when WebSocketState.Open.","Implement reconnect with backoff: on this exception, dispose/recreate the client and re-run ConnectAndSubscribeAsync.","Handle simultaneous closure by catching InvalidOperationException around send calls and re-establishing the connection."],"exampleFix":"// before\nvar client = new SmartRobotWebSocketClient(url, token);\nawait client.SendPingAsync(); // throws 智能机器人长连接尚未建立。\n\n// after\nvar client = new SmartRobotWebSocketClient(url, token);\nawait client.ConnectAndSubscribeAsync();\nif (client.State == WebSocketState.Open)\n{\n    await client.SendPingAsync();\n}","handlingStrategy":"type-guard","validationCode":"// before sending\nif (client.SocketState != WebSocketState.Open)\n{\n    await client.ConnectAndSubscribeAsync(); // reconnect\n}","typeGuard":"bool CanSend(SmartRobotWebSocketClient c) => c != null && c.SocketState == WebSocketState.Open;","tryCatchPattern":"try { await client.SendPingAsync(); }\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"长连接尚未建立\"))\n{\n    await ReconnectAsync(); // re-run ConnectAndSubscribeAsync with backoff\n}","preventionTips":["Always await ConnectAndSubscribeAsync before any send call","Monitor socket state and reconnect on close events","Wrap long-running bots in a supervisor loop that recreates the client on failure","Use a CancellationToken and background task to keep the receive loop alive"],"tags":["websocket","invalid-state-transition","connection","smart-robot"],"backgroundTag":"invalid-state-transition","analyzedSha":"be573f6f94bdbf718dd5f6cdecb137fbc7ff651e","analyzedAt":"2026-09-12T10:01:50.733Z","contentChangedAt":"2026-09-12T10:01:50.733Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}