dotnet/aspnetcore · error · Error

The circuit associated with this dispatcher is no longer…

Error message

The circuit associated with this dispatcher is no longer available.

What it means

`throwIfDispatchingWhenDisposed` guards every dispatcher method (`beginInvokeDotNetFromJS`, `endInvokeJSFromDotNet`, `sendByteArray`) so no JS->.NET calls are sent over a dead circuit. Once `dispose()`/`disposeCore()` sets `_disposed = true`, any subsequent dispatch attempt throws — the underlying SignalR connection is gone and the server-side circuit is being torn down.

Solutions

  1. Cancel/short-circuit JS-side work that may call back into .NET when the circuit is disposing (track a disposed flag in your interop wrapper).
  2. Wrap fire-and-forget callbacks in a guard that checks dispatcher availability before invoking.
  3. Prefer `IAsyncDisposable`/`@implements IAsyncDisposable` to tear down JS resources before the circuit is fully disposed.
  4. Surface this as a recoverable condition in your error UI rather than an unhandled promise rejection.

Example fix

// before
setTimeout(async () => {
  await DotNet.invokeMethodAsync('MyAssembly', 'LateCallback'); // throws if disposed
}, 5000);

// after
let cancelled = false;
export function dispose() { cancelled = true; }
setTimeout(async () => {
  if (cancelled) return;
  try { await DotNet.invokeMethodAsync('MyAssembly', 'LateCallback'); }
  catch { /* circuit gone - ignore */ }
}, 5000);
Defensive patterns

Strategy: try-catch

Validate before calling

let circuitDisposed = false;
export function markDisposed() { circuitDisposed = true; }
async function safeInvoke(assembly: string, method: string, ...args: unknown[]) {
  if (circuitDisposed) return;
  return DotNet.invokeMethodAsync(assembly, method, ...args);
}

Try / catch

try { await DotNet.invokeMethodAsync(asm, method); }
catch (e) { if (/no longer available/.test(e.message)) { /* circuit disposed - swallow */ return; } throw e; }

Prevention

When it happens

Trigger: Any JS interop call routed through the dispatcher after `_disposed` becomes true: a pending `DotNet.invokeMethodAsync`, an `[JSInvokable]` callback completion, or a byte-array send that races with disposal.

Common situations: Long-running JS work calling back into .NET after the user navigated away or the circuit dropped; a `DotNet.invokeMethodAsync` fired from a setTimeout/interval that outlived the circuit; event handlers firing during teardown; streaming data sent after disconnect.

Related errors


AI-assisted analysis of dotnet/aspnetcore@3600ca084e (2026-08-11). Data as JSON: /api/errors/83da029c2d66ed0f. Report an issue: GitHub.

Appendix: 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 JS

View on GitHub (pinned to 3600ca084e)