dotnet/runtime · error · Error

Cannot call synchronous C# method from inside a synchronous

Error message

Cannot call synchronous C# method from inside a synchronous call to a JS method.

What it means

Inside call_delegate, the runtime checks runtimeHelpers.isPendingSynchronousCall. While managed code is already synchronously calling into JS, any further synchronous call from JS back into C# is refused, because the managed thread is blocked waiting on the JS call and cannot re-enter without deadlock or stack corruption.

Source

Thrown at src/mono/browser/runtime/managed-exports.ts:182

        if (error) {
            marshal_exception_to_cs(arg2, error);
        }
        invoke_async_jsexport(runtimeHelpers.ioThreadTID, managedExports.CompleteTask, args, size);
    } finally {
        if (loaderHelpers.is_runtime_running()) Module.stackRestore(sp);

    }
}

// the marshaled signature is: TRes? CallDelegate<T1,T2,T3,TRes>(GCHandle callback, T1? arg1, T2? arg2, T3? arg3)
export function call_delegate (callback_gc_handle: GCHandle, arg1_js: any, arg2_js: any, arg3_js: any, res_converter?: MarshalerToJs, arg1_converter?: MarshalerToCs, arg2_converter?: MarshalerToCs, arg3_converter?: MarshalerToCs) {
    loaderHelpers.assert_runtime_running();
    if (WasmEnableThreads) {
        if (monoThreadInfo.isUI) {
            if (runtimeHelpers.config.jsThreadBlockingMode == JSThreadBlockingMode.PreventSynchronousJSExport) {
                throw new Error("Cannot call synchronous C# methods.");
            } else if (runtimeHelpers.isPendingSynchronousCall) {
                throw new Error("Cannot call synchronous C# method from inside a synchronous call to a JS method.");
            }
        }
    }
    const sp = Module.stackSave();
    try {
        const size = 6;
        const args = alloc_stack_frame(size);

        const arg1 = get_arg(args, 2);
        set_arg_type(arg1, MarshalerType.Object);
        set_gc_handle(arg1, callback_gc_handle);
        // payload arg numbers are shifted by one, the real first is a gc handle of the callback

        if (arg1_converter) {
            const arg2 = get_arg(args, 3);
            arg1_converter(arg2, arg1_js);
        }
        if (arg2_converter) {

View on GitHub (pinned to 290d5ab72c)

Solutions

  1. Defer the inner C# call: wrap it in queueMicrotask/setTimeout(0) or a Promise so it runs after the outer sync call unwinds.
  2. Restructure the interop so JS returns a value to C# instead of re-entering C# synchronously.
  3. Make the C# side async to break the synchronous re-entry chain.

Example fix

// before
// inside a sync JSImport callback:
otherCsExport(); // throws: re-entrant sync call
// after
queueMicrotask(() => otherCsExport());
Defensive patterns

Strategy: validation

Validate before calling

// Do not re-enter C# synchronously from within a sync JS callback. Detect you are inside one by tracking context.
let insideSyncJsCallback = false;
function aroundJsCallback(fn) {
  return (...args) => { const prev = insideSyncJsCallback; insideSyncJsCallback = true; try { return fn(...args); } finally { insideSyncJsCallback = prev; } };
}
function callCsSafe(fn, ...args) {
  if (insideSyncJsCallback) { queueMicrotask(() => fn(...args)); return null; }
  return fn(...args);
}

Try / catch

try {
  otherCsExport();
} catch (e) {
  if (String(e?.message).startsWith('Cannot call synchronous C# method from inside')) {
    queueMicrotask(() => otherCsExport());
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: Re-entrancy: C# synchronously calls a JS method (JSImport/JSExport sync), and inside that JS callback the code synchronously invokes another C# method/delegate while isPendingSynchronousCall is true.

Common situations: A JS interop callback that itself calls back into C# (e.g. a JS function passed to C# that, during the sync call, invokes another exported C# method); event-driven code that nests sync interop.

Related errors


AI-assisted analysis of dotnet/runtime@290d5ab72c (2026-08-06). Data as JSON: /api/errors/fd70fbc56830346a. Report an issue: GitHub.