dotnet/aspnetcore · error · Error
The circuit associated with this dispatcher is no longer ava
Error message
The circuit associated with this dispatcher is no longer available.
What it means
Thrown by throwIfDispatchingWhenDisposed() — invoked from beginInvokeDotNetFromJS, endInvokeJSFromDotNet, and sendByteArray — when _disposed is true. Once a circuit is disposed, the underlying SignalR connection is gone, so any JS->.NET interop dispatch is rejected because there is no server endpoint to receive it.
Source
Thrown at src/Components/Web.JS/src/Platform/Circuits/CircuitManager.ts:528
// Implements DotNet.DotNetCallDispatcher
public endInvokeJSFromDotNet(asyncHandle: number, succeeded: boolean, argsJson: any): void {
this.throwIfDispatchingWhenDisposed();
if (asyncHandle !== 0) {
this.changeActivity(-1);
}
this._connection!.send('EndInvokeJSFromDotNet', asyncHandle, succeeded, argsJson);
}
// Implements DotNet.DotNetCallDispatcher
public sendByteArray(id: number, data: Uint8Array): void {
this.throwIfDispatchingWhenDisposed();
this._connection!.send('ReceiveByteArray', id, data);
}
private throwIfDispatchingWhenDisposed() {
if (this._disposed) {
throw new Error('The circuit associated with this dispatcher is no longer available.');
}
}
public sendLocationChanged(uri: string, state: string | undefined, intercepted: boolean): Promise<void> {
return this._connection!.send('OnLocationChanged', uri, state, intercepted);
}
public sendLocationChanging(callId: number, uri: string, state: string | undefined, intercepted: boolean): Promise<void> {
return this._connection!.send('OnLocationChanging', callId, uri, state, intercepted);
}
public sendJsDataStream(data: ArrayBufferView | Blob, streamId: number, chunkSize: number) {
this.changeActivity(1);
return sendJSDataStream(this._connection!, data, streamId, chunkSize, () => this.changeActivity(-1));
}
public resolveElement(sequenceOrIdentifier: string): LogicalElement {
// It may be a root component added by JSView on GitHub (pinned to 294cab2f9b)
Solutions
- Cancel/short-circuit JS work before disposing the circuit.
- Guard interop calls with a disposed flag in your JS interop wrappers.
- On the .NET side, catch OperationCanceledException / JSDisconnectedException from interop and treat as expected during teardown.
Example fix
// before
await dotNetRef.invokeMethodAsync('OnDone'); // throws if circuit disposed
// after
if (!disposed) {
try { await dotNetRef.invokeMethodAsync('OnDone'); }
catch { /* circuit gone, ignore */ }
} Defensive patterns
Strategy: try-catch
Validate before calling
// Track disposal in JS and short-circuit interop.
let disposed = false;
circuitManager.dispose(); disposed = true;
if (!disposed) { await dotNetRef.invokeMethodAsync('DoWork'); } Try / catch
try { await dotNetRef.invokeMethodAsync('OnDone'); }
catch (e) {
// circuit gone during async work — expected during teardown
if (/no longer available|disposed/i.test(e.message)) return;
throw e;
} Prevention
- Cancel pending JS work before disposing the circuit.
- Catch and swallow interop errors that occur during teardown.
- Maintain a disposed flag and check before every interop call.
When it happens
Trigger: Firing a JS interop call (invokeMethodAsync from JS, or a pending DotNet callback) after the circuit was disposed — e.g. user navigated away, tab closed, circuit dropped, or dispose() ran while async interop was still queued.
Common situations: Long-running JS tasks completing after navigation/circuit loss; event handlers firing during teardown; cleanup callbacks invoking .NET methods on a disposed circuit.
Related errors
- Cannot start a disposed circuit.
- Interop methods are already registered for renderer ${render
- Interop methods are not registered for renderer ${rendererId
- No interop methods are registered for renderer ${rendererId}
- Cannot connect component ${this.localName} to the document a
AI-assisted analysis of dotnet/aspnetcore@294cab2f9b (2026-08-06).
Data as JSON: /api/errors/83da029c2d66ed0f.
Report an issue: GitHub.