323,000次NuGet下载。41个包。覆盖WebRTC、WebGPU、WebTorrent、Canvas、Crypto、IndexedDB、Streams——这是SpawnDev.BlazorJS的生态规模。它的维护者最近发现,微软一个开关正在让这一切归零。
「启用true后,同步C#方法调用直接报错。」
这不是bug。是设计。
Deputy Thread:把.NET赶出主线程
微软的新模型叫「Deputy Thread」。启用多线程后,整个.NET运行时从浏览器主线程迁移到后台Web Worker。UI保持响应,.NET获得真线程,纸面数据漂亮。
代价是JS和.NET分属两个世界。
传统Blazor WASM中,双方共享同一线程。JavaScript调用DotNet.invokeMethod时,CPU从JS栈跳转到C#栈再返回——同步、快速、直接。Deputy Thread模式下,JS在主线程,.NET在Worker。JS发起调用后,主线程必须等待Worker响应。
浏览器禁止主线程阻塞等待Worker。
于是运行时抛出:Error: Cannot call synchronous C# methods. 同步JS-to-.NET通信到此终结。
「全改成异步就行」?浏览器说不
最常见的回应是:把所有调用改成异步。SpawnDev.BlazorJS的维护者指出,这误解了浏览器的事件模型。
JavaScript有大量场景强制要求同步处理。不是边缘案例,是核心功能:
element.addEventListener('submit', (event) => { event.preventDefault(); });
preventDefault()必须在事件处理函数内同步执行。如果等待Worker响应,浏览器已经提交完表单。异步响应到达时,导航已完成,拖拽已结束,其他监听器早已触发。
beforeunload事件更极端——必须同步返回,不允许Promise,不允许等待Worker。
window.addEventListener('beforeunload', (event) => { event.returnValue = 'Are you sure?'; });
大量JavaScript API暴露同步getter和setter。C#包装库若要匹配JS API表面,必须同步读取这些值。Deputy Thread模式下,每次属性访问都变成对Worker的异步往返。
41个包,32万下载,一个开关全埋了
SpawnDev.BlazorJS的困境是生态级的。这个库为Blazor WebAssembly提供JavaScript API的强类型C#包装,让开发者用C#直接操作浏览器原生能力。WebRTC点对点通信、WebGPU图形计算、WebTorrent去中心化下载——这些API的C#封装都依赖同步JS互操作。
微软的线程模型变更,把地基抽走了。
维护者没有公开表态是否重写整个架构。但技术约束是明确的:要么放弃同步API的C#封装(让库失去存在价值),要么说服用户永远不开(在性能敏感场景不现实),要么等待微软提供解决方案。
微软会改吗?Blazor的GitHub议题区里,类似的互操作限制讨论已持续多年。Deputy Thread是.NET 9的正式特性,不是实验性代码。
你的项目里,有没有哪个库正在默默依赖同步JS互操作?下一次dotnet publish,会不会突然报错?
热门跟贴