JeffreySu/WeiXinMPSDK · error · ObjectDisposedException

SmartRobotWebSocketClient

Error message

SmartRobotWebSocketClient

What it means

ThrowIfDisposed throws an ObjectDisposedException naming SmartRobotWebSocketClient when the client has already been disposed (Dispose/DisposeAsync called, _disposed=true). ConnectAndSubscribeAsync and ReceiveLoopAsync call it, so any attempt to connect or keep receiving after disposal fails. It guards the WebSocket and the send semaphore, which are destroyed in Dispose.

Solutions

  1. Never reuse a disposed client: after Dispose, create a new SmartRobotWebSocketClient instance.
  2. Coordinate shutdown: await/cancel the receive loop task before disposing the client.
  3. Guard call sites with a lifetime check (e.g. try/catch ObjectDisposedException or a _disposed-aware wrapper).
  4. In DI scenarios, register with the correct lifetime and implement IHostedService so disposal happens after the loop stops.

Example fix

// before
client.Dispose();
await client.ConnectAndSubscribeAsync(); // ObjectDisposedException

// after
await receiveLoopTask; // stop the loop first
client.Dispose();
client = new SmartRobotWebSocketClient(url, token);
await client.ConnectAndSubscribeAsync();
Defensive patterns

Strategy: type-guard

Validate before calling

// track disposal yourself
private bool _clientClosed;
// before reuse
if (_clientClosed) { _client = new SmartRobotWebSocketClient(url, token); }

Type guard

bool IsUsable(SmartRobotWebSocketClient c) => c != null && !c.IsDisposed; // expose if possible; otherwise track via wrapper

Try / catch

try { await client.ConnectAndSubscribeAsync(); }
catch (ObjectDisposedException)
{
    client = new SmartRobotWebSocketClient(url, token);
    await client.ConnectAndSubscribeAsync();
}

Prevention

When it happens

Trigger: Calling ConnectAndSubscribeAsync after Dispose()/DisposeAsync(); the ReceiveLoopAsync running in background while the client is disposed elsewhere (e.g. host shutdown) and it touches the disposed instance.

Common situations: ASP.NET Core DI container disposing a singleton/client at shutdown while its receive loop is still running; using statements that dispose the client too early while a background loop holds a reference; restart logic that disposes then accidentally reuses the old instance instead of creating a new one.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of JeffreySu/WeiXinMPSDK@be573f6f94 (2026-09-12). Data as JSON: /api/errors/6beaff899e28a56f. Report an issue: GitHub.

Appendix: source

Thrown at src/Senparc.Weixin.Work/Senparc.Weixin.Work/AdvancedAPIs/SmartRobot/SmartRobotWebSocketClient.cs:267

        /// <summary>
        /// 创建智能机器人 WebSocket 请求 ID。
        /// </summary>
        /// <returns>32 位无连字符 GUID 请求 ID。</returns>
        public static string CreateRequestId() => Guid.NewGuid().ToString("N");

        private static ClientWebSocket CreateSocket()
        {
            var socket = new ClientWebSocket();
            socket.Options.KeepAliveInterval = TimeSpan.FromSeconds(20);
            return socket;
        }

        private void ThrowIfDisposed()
        {
            if (_disposed)
            {
                throw new ObjectDisposedException(nameof(SmartRobotWebSocketClient));
            }
        }

        /// <summary>
        /// 释放 WebSocket 连接和发送锁。
        /// </summary>
        public void Dispose()
        {
            if (_disposed) return;
            _disposed = true;
            _socket.Dispose();
            _sendLock.Dispose();
        }

        /// <summary>
        /// 异步释放 WebSocket 连接和发送锁。
        /// </summary>
        /// <returns>表示释放操作的任务。</returns>

View on GitHub (pinned to be573f6f94)