Fusion 是一个 .NET 库,它实现了 🦄 分布式反应式记忆化 (DREAM) – 这是一个新颖的抽象概念,有点类似于 MobX 或 Flux,但设计用于处理跨越后端微服务、API 服务器,甚至延伸到应用程序每个客户端的任意大规模状态。
Fusion 用一个工具解决了一系列臭名昭著的难题:
| 问题 | 所以你不需要... |
|---|---|
| 📇 缓存 | Redis, memcached, ... |
| 🤹 实时缓存失效 | 没有好的解决方案 - <br/>这是一个臭名昭著的难题 |
| 🚀 实时更新 | SignalR, WebSockets, gRPC, ... |
| 🤬 网络碎片化 | 大量代码 |
| 🔌 离线模式支持 | 大量代码 |
| 📱 客户端状态管理 | MobX, Flux/Redux, Recoil, ... |
| 💰 Blazor WebAssembly、Server 和 Hybrid/MAUI 的单一代码库 | 没有好的替代方案 |
最棒的是:Fusion 为你透明地处理所有这些,所以基于 Fusion 的代码几乎与不涉及它的代码相同。你只需要:
IComputeService(一个标记接口),以确保在编译时为它生成调用拦截代理。[ComputeMethod] 标记需要"Fusion 行为"的方法 + 将它们声明为 virtualserviceCollection.AddFusion().AddService<MyService>() 注册服务当调用 [ComputeMethod] 时,魔法就发生了:
(serviceInstance, method, args...) 缓存键)仍然一致时,Fusion 会立即返回它,而不让方法运行。[ComputeMethod] 调用期间触发的一个 [ComputeMethod] 调用。第二步允许 Fusion 跟踪当其中一个值改变时,哪些值预期会改变。这与批次可追溯性非常相似,但是针对任意函数而不是制造过程实现的。
拼图的最后一块是 Computed.Invalidate() 块,它允许将缓存结果标记为"与基本事实不一致"。以下是你如何使用它:
var avatars = await GetUserAvatars(userId); using (Computed.Invalidate()) { // 在此块内调用的任何 [ComputeMethod] 都不会正常运行, // 而是使相同调用的结果失效。 // 这些调用同步完成并返回已完成的 Task<TResult>, // 所以你不需要等待它们。 _ = userService.GetUser(userId); foreach (var avatar in avatars) _ = userAvatarService.GetAvatar(userId, avatar.Id); }
失效总是传递性的: 如果 GetUserProfile(3) 调用 GetUserAvatar("3:ava1"),而 GetUserAvatar("3:ava1") 失效,GetUserProfile(3) 也会失效。
为了使其工作,Fusion 维护了一个类似字典的结构,用于跟踪最近和"被观察"的调用结果:
(serviceInstance, method, call arguments...)Computing、Consistent、Invalidated)和依赖-被依赖链接。Computed<T> 实例几乎是不可变的:一旦构造,它们只能转换到 Inconsistent 状态。你可以这样"提取"某个调用"背后"的 Computed<T> 实例:
var computed1 = await Computed.Capture(() => GetUserProfile(3)); // 你可以等待它的失效: await computed1.WhenInvalidated(); Assert.IsFalse(computed1.IsConsistent()); // 并重新计算它: var computed2 = await computed1.Recompute();
所以任何 Computed<T> 都是可观察的。而且,它可以是远程 Computed<T> 实例的"副本",在你的本地进程中镜像其状态,所以依赖图可以是分布式的。为了实现这一点,Fusion 使用了自己的基于 WebSocket 的 RPC 协议,它与任何其他 RPC 协议非常相似:
步骤 3 在网络流量方面并没有改变太多:每次调用最多只有一条额外消息(即最坏情况下是 3 条消息而不是 2 条)。但这个小小的添加允许计算服务客户端准确知道给定的缓存调用结果何时变得不一致。
步骤 3 的存在产生了巨大的差异:任何缓存的且仍然一致的结果都与你从远程服务器获得的数据一样好,对吧?所以在本地解决"命中"这样结果的调用是完全可以的,不会产生网络往返!
最后,任何计算服务客户端的行为都类似于本地计算服务。看看这段代码:
string GetUserName(id) => (await userService.GetUser(id)).Name;
你无法判断这里的 userService 是本地计算服务还是计算服务客户端,对吧?
IUserService)。但实现不同:Fusion 服务客户端通过 fusion.AddClient<TInterface>() 注册,而服务器通过 fusion.AddServer<TInterface, TService>() 注册。userService 的每次调用都会立即终止GetUserName 是另一个计算服务(本地服务)的方法,支持它所做的 GetUser(id) 调用的计算值会自动扩展 GetUserName(id) 调用的 Fusion 依赖图!因此,Fusion 抽象了服务的"位置",而且比传统的 RPC 代理做得更好:Fusion 代理默认不会"碎片化"!
<img align="right" width="150" src="https://yellow-cdn.veclightyear.com/835a84d5/b6bb7a4c-a40c-4e1b-be6d-d7b4db9d470c.jpg"/> 如果你喜欢幻灯片,请查看 "为什么实时 Web 应用需要 Blazor 和 Fusion?"演讲 - 它解释了我们解决的许多问题是如何联系在一起的,Fusion 如何解决根本原因,以及如何用 C# 编写 Fusion 关键抽象的简化版本。
幻灯片稍微有些过时 - 例如,现在Fusion客户端使用
Stl.Rpc而不是HTTP与服务器通信,但它们涵盖的所有概念仍然完整。
查看示例;本文档后面会介绍其中的一些内容。
**这一切听起来太好了,简直不像是真的,对吧?**这就是为什么在本文档的剩余部分有大量的视觉证明。但如果你在Fusion的源代码或示例中发现任何令人担忧的地方,请随时在Discord上向我们提出质疑!
让我们从一些重量级的内容开始:
看看Actual Chat – 一个由Fusion背后的团队打造的全新聊天应用。
Actual Chat融合了实时音频、实时转录和AI辅助, 让你以最高效率进行沟通。 它拥有WebAssembly、iOS、Android和Windows的客户端, 在这些平台上实现了近100%的代码共享。 除了实时更新,它的一些功能,如离线模式, 都是由Fusion驱动的。
我们在这里发布了一些来自Actual Chat代码库的示例, 加入这个聊天室,了解我们如何在真实应用中使用它。
现在,来看示例:
下面是Fusion+Blazor示例 向3个浏览器窗口提供实时更新:

<img src="https://img.shields.io/badge/-Live!-red" valign="middle"> 立即体验 此示例的在线版本!
该示例支持Blazor Server和Blazor WebAssembly 两种托管模式。 即使在不同窗口中使用不同的模式, Fusion仍然能保持共享状态的每一位同步, 包括登录状态:

**是的,它快得令人难以置信。**以下是Actual Chat上最频繁的RPC调用之一的调用持续时间分布:

IChats.GetTile读取一个小的"聊天瓦片" - 通常是固定在特定ID范围内的5个条目,因此可以高效缓存。即使对于这些调用,典型的响应时间也几乎无法测量:每个X轴标记都比前一个大10倍,所以你看到的最高峰值在0.03ms!
接下来在约4-5ms处的凸起是服务实际访问数据库的时间 - 也就是说,这是你在没有Fusion的情况下预期看到的时间。不过,负载会高得多,因为你在这个图表上看到的调用是"抵达"服务器的调用 - 换句话说,它们没有被客户端或其Fusion服务消除。
Fusion测试套件中的一个小型综合基准测试 比较了基于"原始"Entity Framework Core的 数据访问层(DAL)与其依赖Fusion的版本:
| 每秒调用次数 | PostgreSQL | MariaDB | SQL Server | Sqlite |
|---|---|---|---|---|
| 单个读取器 | 1.02K | 645.77 | 863.33 | 3.79K |
| 960个读取器(高并发) | 12.96K | 14.52K | 16.66K | 16.50K |
| 单个读取器 + Fusion | 9.54M | 9.28M | 9.05M | 8.92M |
| 960个读取器 + Fusion | 145.95M | 140.29M | 137.70M | 141.40M |
在Ryzen Threadripper 3960X上运行此测试的原始输出在这里。读取器的数量乍看起来很疯狂,但它是为了最大化DAL的非Fusion版本的输出而调整的(读取器是异步的,所以它们大部分时间都在等待数据库响应)。
Fusion的透明缓存确保你的代码产生的每个API调用结果都被缓存,而且,即使在重新计算这些结果时,它们大多使用其他缓存的依赖项,而不是访问速度慢得多的存储(在这种情况下是数据库)。
有趣的是,即使没有依赖项的"层"(只考虑"零层"),Fusion也能将此测试运行的API调用速度提高8,000到12,000倍。
msbuild,但是用于你的方法调用结果:已计算且一致的内容永远不会被重新计算。Stl.Interception库来拦截方法调用,虽然还没有基准测试,但这些是.NET上可用的最快的调用拦截器 - 它们比如Castle.DynamicProxy提供的拦截器稍快。它们不会装箱调用参数,每次调用只需要1次分配。Stl.Rpc(Fusion负责RPC调用的部分)也是如此。它的初步基准测试结果显示它比SignalR快约1.5倍,比gRPC快约3倍。Stl.Rpc使用.NET上可用的最快序列化器 – 默认为MemoryPack(它不需要运行时IL Emit),尽管你也可以使用MessagePack(它稍快,但需要IL Emit)或你喜欢的任何其他序列化器。是的。Fusion做的事情类似于任何[MMORPG]游戏引擎所做的:尽管完整的游戏状态是巨大的,但仍然可以为100万+玩家实时运行游戏,因为每个玩家只观察到完整游戏状态的一小部分,因此你只需要确保被观察的部分状态适合内存即可。
这正是Fusion所做的:
查看教程中的"扩展Fusion服务"部分,了解Fusion如何扩展的更详细描述。
一个典型的Compute Service如下所示:
public class ExampleService : IComputeService { [ComputeMethod] public virtual async Task<string> GetValue(string key) { // 此方法从非Fusion"源"读取数据, // 因此在写入时需要失效(参见SetValue) return await File.ReadAllTextAsync(_prefix + key); } ```csharp [ComputeMethod] public virtual async Task<string> GetPair(string key1, string key2) { // 此方法只使用其他 [ComputeMethod] 或静态数据, // 因此不需要在写入时进行失效处理 var v1 = await GetNonFusionData(key1); var v2 = await GetNonFusionData(key2); return $"{v1}, {v2}"; } public async Task SetValue(string key, string value) { // 此方法更改了 GetValue 和 GetPair 读取的数据, // 但由于 GetPair 使用了 GetValue,因此一旦我们使 GetValue 失效, // GetPair 也会自动失效。 await File.WriteAllTextAsync(_prefix + key, value); using (Computed.Invalidate()) { // 这是使此方法更改的内容失效的方式。 // 调用参数很重要:您只使具有匹配参数的调用结果失效, // 而不是每个 GetValue 调用的结果! _ = GetValue(key); } } }
[ComputeMethod] 表示每次调用此方法时,其结果都由 [Computed Value] 支持,因此它在运行时捕获依赖关系,并在当前计算值仍然一致时立即返回结果。
计算服务的注册方式几乎类似于单例:
var services = new ServiceCollection(); var fusion = services.AddFusion(); // 可以多次调用 // ~ 类似于 service.AddSingleton<[TService, ]TImplementation>() fusion.AddService<ExampleService>();
查看 HelloBlazorServer 示例 中的 CounterService 以了解计算服务的实际代码。
现在,我猜你很好奇使用 Fusion 的 UI 代码是什么样的。你会惊讶地发现,它简单得不能再简单了:
// MomentsAgoBadge.razor @inherits ComputedStateComponent<string> @inject IFusionTime _fusionTime <span>@State.Value</span> @code { [Parameter] public DateTime Value { get; set; } protected override Task<string> ComputeState() => _fusionTime.GetMomentsAgo(Value) ; }
MomentsAgoBadge 是一个 Blazor 组件,用于显示 "N [秒/分钟/...] 前" 的字符串。上面的代码与其 实际代码 几乎完全相同,只是后者由于处理 null 而稍微复杂一些。
你可以看到它使用了 IFusionTime - 这是一个内置的计算服务,提供 GetUtcNow 和 GetMomentsAgo 方法。你可能猜到了,这些方法的结果会自动失效;查看 FusionTime 服务 以了解其工作原理。
但这里重要的是 MomentsAgoBadge 继承自 ComputedStateComponent<T> - 这是一个抽象类型,提供了 ComputeState 方法。你可能猜到了,这个方法的行为类似于 [Compute Method]。
ComputedStateComponent<T> 暴露了 State 属性(类型为 ComputedState<T>),允许你通过其 Value 属性获取 ComputeState() 的最新输出。"State" 是 Fusion 的另一个关键抽象 - 它实现了一个"等待失效并重新计算"循环,类似于这个:
var computed = await Computed.Capture(_ => service.Method(...)); while (true) { await computed.WhenInvalidated(); computed = await computed.Update(); }
唯一的区别是它以更健壮的方式执行此操作 - 特别是,它允许你控制失效和更新之间的延迟,访问最新的非错误值等。
最后,ComputedStateComponent 在其 State 更新时自动调用 StateHasChanged(),以确保显示新值。
因此,如果你使用 Fusion,你就不需要在 UI 中编写任何反应代码。 反应(即部分更新和重新渲染)会自动发生,这是因为依赖链将 UI 组件与它们使用的数据提供者连接起来,而数据提供者又与它们使用的数据提供者相连,以此类推 - 直到最基本的"成分提供者",即在变化时失效的计算方法。
如果你想看到更多类似简单的 UI 组件示例,可以查看:
实时通常意味着你使用事件向每个可能受此变化影响的客户端传递变更通知,因此你必须:
而 Fusion 使用单一的抽象来解决所有这些问题,允许它自动识别和跟踪数据依赖关系。
Fusion 允许你创建真正独立的 UI 组件。 你可以将它们嵌入到 UI 的任何部分,而不需要担心它们如何相互交互。
这使得 Fusion 非常适合 Blazor 上的微前端: 在这里,创建松耦合的 UI 组件的能力至关重要。
除此之外,如果你的失效逻辑正确, Fusion 保证你的 UI 状态最终是一致的。
你可能认为所有这些只在 Blazor Server 模式下工作。 但事实并非如此,所有这些 UI 组件在 Blazor WebAssembly 模式下也能工作,这是 Fusion 提供的另一个独特功能。 任何 [Compute Service] 都可以替换为 [Compute Service Client],它不仅仅是代理调用,还完全 消除了你在常规客户端代理中可能遇到的频繁通信问题。
附言: 如果你已经花时间了解了Fusion, 请帮助我们改进它,完成Fusion反馈表 (1…3分钟)。


职场AI,就用扣子
AI办公助手,复杂任务高效处理。办公效率低?扣子空间AI助手支持播客生成、PPT制作、网页开发及报告写作,覆盖科研、商业、舆情等领域的专家Agent 7x24小时响应,生活工作无缝切换,提升50%效率!


多风格AI绘画神器
堆友平台由阿里巴巴设计团队创建,作为一款AI驱动的设计工具,专为设计师提供一站式增长服务。功能覆盖海量3D素材、AI绘画、实时渲染以及专业抠图,显著提升设计品质和效率。平台不仅提供工具,还是一个促进创意交流和个人发展的空间,界面友好,适合所有级别的设计师和创意工作者。


零代码AI应用开发平台
零代码AI应用开发平台,用户只需一句话简单描述需求,AI能自动生成小程序、APP或H5网页应用,无需编写代码。


免费创建高清无水印Sora视频
Vora是一个免费创建高清无水印Sora视频的AI工具


最适合小白的AI自动化工作流平台
无需编码,轻松生成可复用、可变现的AI自动化工作流

大模型驱动的Excel数据处理工具
基于大模型交互的表格处理系统,允许用户通过对话方式完成数据整理和可视化分析。系统采用机器学习算法解析用户指令,自动执行排序、公式计算和数据透视等操作,支持多种文件格式导入导出。数据处理响应速度保持在0.8秒以内,支持超过100万行数据的即时分析。


AI辅助编程,代码自动修复
Trae是一种自适应的集成开发环境(IDE),通过自动化和多元协作改变开发流程。利用Trae,团队能够更快速、精确地编写和部署代码,从而提高编程效率和项目交付速度。Trae具备上下文感知和代码自动完成功能,是提升开发效率的理想工具。


AI论文写作指导平台
AIWritePaper论文写作是一站式AI论文写作辅助工具,简化了选题、文献检索至论文撰写的整个过程。通过简单设定,平台可快速生成高质量论文大纲和全文,配合图表、参考文献等一应俱全,同时提供开题报告和答辩PPT等增值服务,保障数据安全,有效提升写作效率和论文质量。


AI一键生成PPT,就用博思AIPPT!
博思AIPPT,新一代的AI生成PPT平台,支持智能生成PPT、AI美化PPT、文本&链接生成PPT、导入Word/PDF/Markdown文档生成PPT等,内置海量精美PPT模板,涵盖商务、教育、科技等不同风格,同时针对每个页面提供多种版式,一键自适应切换,完美适配各种办公场景。


AI赋能电商视觉革命,一站式智能商拍平台
潮际好麦深耕服装行业,是国内AI试衣效果最好的软件。使用先进AIGC能力为电商卖家批量提供优质的、低成本的商拍图。合作品牌有Shein、Lazada、安踏、百丽等65个国内外头部品牌,以及国内10万+淘宝、天猫、京东等主流平台的品牌商家,为卖家节省将近85%的出图成本,提升约3倍 出图效率,让品牌能够快速上架。
最新AI工具、AI资讯
独家AI资源、AI项目落地

微信扫一扫关注公众号