ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Orleans 自定义 Grain 调用返回类型实战:基于 GrainCall\<T\> 构建任务型 awaitable 契约

Orleans 自定义 Grain 调用返回类型实战:基于 GrainCall\<T\> 构建任务型 awaitable 契约 后端微服务【免费下载链接】orleansCloud Native application framework for .NET项目地址https://gitcode.com/gh_mirrors/or/orleans点击查看免费下载本文以 samples/CustomGrainCallReturnType 示例为蓝本讲解如何在 Orleans 的 grain 接口中直接使用自定义返回类型GrainCallT以及 Orleans 源码生成器如何据此生成请求类型、代理方法与目标侧调度逻辑。读完本文你将掌握自定义调用返回类型的注册方式InvokableBaseTypeAttribute、ReturnValueProxyAttribute、请求基类的调用/响应适配原理以及如何把这种扩展点复用到流式、惰性提交等更复杂的调用契约中。示例要解决的问题Orleans 为 grain 接口方法生成的调用面默认围绕TaskT、ValueTaskT与IAsyncEnumerableT展开。当某个库需要一种带有明确语义的调用抽象时——例如调用立即启动、支持多次 await、以任务承载远端终态结果——就可以让 grain 接口方法直接返回自定义的 awaitable 类型由源码生成器配合自定义的请求基类完成整个 RPC 链路。本示例在仓库samples/CustomGrainCallReturnType目录下由四个文件构成一个完整可运行的程序文件职责GrainCall.cs定义GrainCallT返回类型与GrainCallRequestT请求基类CalculatorGrain.cs定义ICalculatorGrain接口与CalculatorGrain实现Program.cs客户端入口发起调用并观察成功与失败路径CustomGrainCallReturnType.csproj项目文件net10.0引用Microsoft.Orleans.Server与Microsoft.Extensions.Hosting示例的 grain 契约非常直观public interface ICalculatorGrain : IGrainWithStringKey { GrainCallint Add(int left, int right); GrainCallint Fail(string message); }接口方法既不返回Taskint也不返回ValueTaskint而是直接返回GrainCallint。对调用方来说它依然是一个可以被await的 awaitable但底层是示例库自己定义的调用模型。运行示例示例面向 .NET 10见 CustomGrainCallReturnType.csproj 中的TargetFramework并采用UseLocalhostClustering()单进程本地集群见 Program.cs因此无需任何外部服务即可运行dotnet run --project CustomGrainCallReturnType.csproj客户端 Program.cs 依次做了三件事通过IGrainFactory.GetGrainICalculatorGrain(calculator)拿到字符串主键的 grain 引用await calculator.Add(20, 22)打印成功结果20 22 42捕获calculator.Fail(The grain reported a sample failure.)抛出的InvalidOperationException打印观察到的失败消息。这组代码完整验证了自定义返回类型在成功值传递与远端异常回传两条路径上的行为。GrainCallT任务型 awaitable 的完整实现GrainCall.cs 中GrainCallT的全部逻辑只有约 20 行核心是用一个TaskT?作为唯一的内部状态[InvokableBaseType( typeof(GrainReference), typeof(GrainCall), typeof(GrainCallRequest))] public sealed class GrainCallT { private readonly TaskT? _task; private GrainCall(TaskT? task) _task task; public bool IsCompleted _task.IsCompleted; public TaskT? AsTask() _task; public TaskAwaiterT? GetAwaiter() _task.GetAwaiter(); public static GrainCallT FromResult(T? value) new(Task.FromResult(value)); public static GrainCallT FromTask(TaskT? task) new(task); internal static GrainCallT FromInvocation(ValueTaskT? invocation) new(invocation.AsTask()); }逐项解读这个类型的设计意图GetAwaiter()委托给底层TaskT?.GetAwaiter()使GrainCallT天然支持await、await foreach之外的TaskAwaiterT?语义例如IsCompleted轮询或OnCompleted续接AsTask()将包装暴露为普通TaskT?便于与Task.WhenAll等组合子互操作FromResult/FromTask是静态工厂供 grain 实现侧构造已完成的调用示例的Add方法正是用GrainCallint.FromResult(left right)直接返回结果FromInvocation是internal方法专门供生成的请求基类InitializeRequest调用把运行时返回的ValueTaskT?转入任务包装构造器为private外部只能通过工厂方法创建实例保证内部状态不可被随意替换。在接口声明之外这个类的另一个关键身份是它顶部的特性[InvokableBaseType( typeof(GrainReference), typeof(GrainCall), typeof(GrainCallRequest))]InvokableBaseTypeAttribute定义于 src/Orleans.Serialization.Abstractions/Annotations.cs构造参数依次为代理基类GrainReference、返回类型开放的GrainCall与请求基类开放的GrainCallRequest。它把凡是由GrainReference派生的代理、且方法返回GrainCallT一族的调用映射到GrainCallRequestT请求基类上源码生成器在为每个GrainCallT方法生成请求类型时会用同样的T关闭GrainCallRequestT。GrainCallRequestT请求基类的双向适配GrainCallRequestTGrainCall.cs是整个扩展点最核心的部分它同时在调用侧和目标侧工作。其完整定义如下[SerializerTransparent] [ReturnValueProxy(nameof(InitializeRequest))] public abstract class GrainCallRequestT : RequestBase { [NonSerialized] private readonly IGrainReferenceRuntime _runtime; [GeneratedActivatorConstructor] protected GrainCallRequest(IGrainReferenceRuntime runtime) _runtime runtime; public GrainCallT InitializeRequest(GrainReference proxy) GrainCallT.FromInvocation( _runtime.InvokeMethodAsyncT(proxy, this, Options)); public sealed override ValueTaskResponse Invoke() { try { return CompleteAsync(InvokeInner()); } catch (Exception exception) { return ValueTask.FromResult(Response.FromException(exception)); } } private static async ValueTaskResponse CompleteAsync(GrainCallT call) { try { return Response.FromResult(await call); } catch (Exception exception) { return Response.FromException(exception); } } protected abstract GrainCallT InvokeInner(); }下面把这个类型拆成三个层面理解。1. 调用侧初始化并提交请求ReturnValueProxyAttribute定义于 src/Orleans.Serialization.Abstractions/Annotations.cs告诉源码生成器生成的代理方法不要走默认的提交即返回路径而是调用InitializeRequest并把它的返回值直接交给应用程序。InitializeRequest是调用发生的交接点public GrainCallT InitializeRequest(GrainReference proxy) GrainCallT.FromInvocation( _runtime.InvokeMethodAsyncT(proxy, this, Options));_runtime是IGrainReferenceRuntime其接口定义在 src/Orleans.Core.Abstractions/Runtime/IGrainReferenceRuntime.cs其中InvokeMethodAsyncT同文件第 22 行负责把请求对象提交到 Orleans 运行时this就是刚生成的请求实例Options携带调用选项FromInvocation把InvokeMethodAsyncT返回的ValueTaskT?包装成GrainCallT返回。由于ValueTask对应的底层任务会缓存终态结果所以包装后的GrainCallT可以被多次 await 且始终看到同一个结果。在构造上_runtime字段被标记为[NonSerialized]——它是运行时注入的服务引用绝不参与序列化真正作为消息体跨进程传输的是生成器为每个方法生成的参数字段。2. 目标侧等待实现结果并转换为响应Invoke()是IInvokable的入口目标 silo 在解析激活并经过入站过滤器后会调用它这一链路在 docs/site/src/content/docs/implementation/serialization.md 中有完整描述。GrainCallRequestT的逻辑分两层同步异常Invoke()先用try/catch包住InvokeInner()一旦 grain 实现同步抛错立即通过Response.FromException转成异常响应异步结果CompleteAsync先await call等待实现返回的GrainCallT完成成功则Response.FromResult(await call)失败则同样转为异常响应。Response的这两个工厂方法定义于 src/Orleans.Serialization/Invocation/Response.csFromException第 26 行创建异常响应FromResultTResult第 34 行从响应池取出ResponseTResult并写入结果值。生成的请求类型最终以Response为消息体序列化回调用方。3. 生成的请求类型GrainCallRequestT是abstract基类其中protected abstract GrainCallT InvokeInner();是留给源码生成器实现的方法。生成器为每个接口方法如Add、Fail生成一个具体的请求类型它用生成的字段保存各方法参数这部分才是序列化载荷用与 grain 方法一致的签名实现InvokeInner直接调度到 grain 实现上的对应方法继承基类Invoke/CompleteAsync的完成与异常转换策略。也就是说调用侧如何把请求送出去与目标侧如何把结果收回来完全由请求基类决定而参数是什么、调用 grain 的哪个方法由生成器产出的派生类型决定。构造器注入GeneratedActivatorConstructor基类构造器标有GeneratedActivatorConstructorAttribute定义于 src/Orleans.Serialization.Abstractions/Annotations.cs。它从依赖注入容器解析IGrainReferenceRuntime并传给生成的请求类型因此请求激活时无需手写运行时钩子如果请求基类不需要任何服务一个无参构造器同样可用。完整调用时序把上面两节拼起来一次await calculator.Add(20, 22)的完整生命周期如下生成代理生成器为ICalculatorGrain.Add生成代理方法与AddRequest : GrainCallRequestint调用侧初始化代理方法创建AddRequest把参数20、22拷入生成字段调用InitializeRequest(this)提交InitializeRequest通过IGrainReferenceRuntime.InvokeMethodAsync提交请求并立即返回GrainCallint给应用程序传输请求作为消息体经网络送达目标激活响应到达后GrainCallT内部的Taskint?完成调用方await继续目标侧调度目标运行时解析激活、安装目标、执行入站过滤器然后调用AddRequest.Invoke()结果转换InvokeInner()调用CalculatorGrain.AddCompleteAsync等待返回的GrainCallint用Response.FromResult编码结果回传失败路径Fail同步抛出的InvalidOperationException被Invoke()的 catch 捕获经Response.FromException回传调用方await时重新抛出该异常Program.cs中的catch (InvalidOperationException)正是观察这一点。其中第 4 步的特殊之处对于标记了ReturnValueProxyAttribute的返回类型调用侧立即返回 awaitable的交接由初始器完成但目标侧仍然按照请求基类的契约接收与分发请求。这一点在 docs/site/src/content/docs/implementation/serialization.md 的代理/请求/分发/响应路径说明中有明确交代。注册规则与更复杂的自定义调用契约code-generation-customization.md 是这份示例对应的完整指南其中说明了几条值得牢记的规则注册位置InvokableBaseTypeAttribute可出现在方法特性、返回类型、程序集或通过DefaultInvokableBaseTypeAttribute代理基类四个位置示例采用的是返回类型这一位置即由返回类型库自己声明其适配器解析优先级生成器先看精确构造的返回类型匹配如GrainCallint再看开放泛型匹配如GrainCall同一轮内优先级为方法特性 返回类型 程序集注册 代理默认泛型约束开放泛型映射要求请求基类与返回类型元数一致生成器会用返回类型实参关闭请求基类并逐条验证泛型约束标识稳定性生成的请求类型即序列化消息体其复合标识包含调用标记、代理标识、接口类型与方法标识跨版本滚动升级时须保持序列化成员 ID 与别名稳定。GrainCallT只是任务型、立即提交这一种策略。指南同时指出自定义适配器完全可以实现其他策略例如惰性提交创建时暂不发起、显式Start才提交、流式返回或订阅式返回——这些策略都应作为公开返回类型的契约明确写出并妥善处理 grain 激活生命周期、释放、背压与放弃消费等问题。示例的GrainCallT则明确承诺了五条行为完成代理初始器立即提交一次请求任务在 Orleans 响应到达时完成失败grain 同步抛错与 await 期间的失败都会成为异常响应并在调用方重新抛出取消依赖 grain 方法参数中的CancellationToken参与 Orleans 协同取消包装本身不引入独立的取消源生命周期提交后请求归运行时所有包装只持有代表该调用的任务并发任务型包装支持多个 awaiter 观察同一终态结果始终代表同一次调用、绝不重放提交。如果你计划把这种模式封装为跨程序集的适配器库官方还要求将其作为返回类型 请求基类 注册 序列化元数据的版本化兼容单元整体发布并验证调用方与 silo 相邻版本共存时的行为一致性。这与示例本身构成了一条从跑通一个自定义返回类型到发布一个可复用调用契约库的完整路径。深入阅读docs/site/src/content/docs/grains/code-generation-customization.md自定义 Orleans 序列化代码生成注册规则、优先级、校验要求、跨程序集注册docs/site/src/content/docs/implementation/serialization.md生成代理、请求、分发与响应的底层路径src/Orleans.Serialization.Abstractions/Annotations.csInvokableBaseTypeAttribute、ReturnValueProxyAttribute、GeneratedActivatorConstructorAttribute等扩展点特性的定义src/Orleans.Core.Abstractions/Runtime/IGrainReferenceRuntime.cs请求提交运行时的接口定义src/Orleans.Serialization/Invocation/Response.csResponse.FromResult/Response.FromException响应编码实现。赞分享后端微服务【免费下载链接】orleansCloud Native application framework for .NET项目地址https://gitcode.com/gh_mirrors/or/orleans点击查看免费下载相关推荐Orleans 序列化代码生成自定义为 Grain 调用定义自定义返回类型Orleans 序列化代码生成自定义为 Grain 调用定义自定义返回类型 Orleans 的源生成器source generator会为每个 Grain后端微服务Orleans 诊断 ORLEANS0026 完全指南自定义 Grain 调用返回类型的 InvokableBaseType 映射失效与修复Orleans 诊断 ORLEANS0026 完全指南自定义 Grain 调用返回类型的 InvokableBaseType 映射失效与修复 导读 ORLEA后端微服务Orleans 响应流式处理使用 IAsyncEnumerable\T\ 增量返回 grain 调用结果Orleans 响应流式处理使用 IAsyncEnumerable\T\ 增量返回 grain 调用结果 响应流Response Streaming 是后端微服务上一篇Specialized .NET Stream Classes - 开源项目推荐下一篇用免费猫抓资源嗅探器把网页视频存下来新手上手指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表