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
- Never reuse a disposed client: after Dispose, create a new SmartRobotWebSocketClient instance.
- Coordinate shutdown: await/cancel the receive loop task before disposing the client.
- Guard call sites with a lifetime check (e.g. try/catch ObjectDisposedException or a _disposed-aware wrapper).
- 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
- Cancel the receive loop task before calling Dispose
- Never use a client after Dispose; treat it as one-shot per connection
- In DI, use IHostedService to order shutdown: stop loop, then dispose
- Wrap the client in an owner class that serializes connect/dispose calls
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)