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
- Defer the inner C# call: wrap it in queueMicrotask/setTimeout(0) or a Promise so it runs after the outer sync call unwinds.
- Restructure the interop so JS returns a value to C# instead of re-entering C# synchronously.
- 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
- Never call back into C# synchronously from a JS function that C# is calling synchronously.
- Defer nested interop with queueMicrotask/Promise.resolve().then().
- Design interop so JS returns values to C# rather than invoking C# mid-callback.
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
- Cannot call synchronous C# methods.
- ${part} not found while looking up ${function_name}
- ${function_name} must be a Function but was ${typeof fn}
- Invalid jsThreadBlockingMode
- NotImplementedException: bigint
AI-assisted analysis of dotnet/runtime@290d5ab72c (2026-08-06).
Data as JSON: /api/errors/fd70fbc56830346a.
Report an issue: GitHub.