调用上下文
定义
命名空间:TouchSocket.Rpc
程序集:TouchSocket.Rpc.dll
一、说明
Rpc服务的调用是无状态的,即只知道当前服务被调用,但无法得知是被谁调用,这个问题给日志记录、Rpc回调等带来了很多麻烦事。所以我们在设计Rpc时,也设计了调用上下文获取。
在上下文中可以获得调用者Caller
等信息,可以获得调用的IP或其他信息。
调用上下文(ICallContext
)实例每次请求都会创建,所以,不要在上下文中存放一些需要长期使用的数据。
二、使用
2.1 通过传参获得
当服务是单例注册时,服务方法可能会被并发调用,所以,调用上下文必须从参数 获得传入。
步骤:
定义的服务方法的第一个参数
使用ICallContext
或其派生类(例如:DmtpRpc
可以使用IDmtpRpcCallContext
)。
public class MyRpcServer : RpcServer
{
[Description("登录")]
[DmtpRpc]
public bool Login(ICallContext callContext,string account,string password)
{
if (callContext.Caller is TcpDmtpSessionClient)
{
Console.WriteLine("TcpDmtpRpc请求");
}
if (account=="123"&&password=="abc")
{
return true;
}
return false;
}
}
2.2 通过瞬时生命周期获取
当服务是瞬态注册时,每次调用服务会创建新的实例,所以当前方法只会被当前调用者拥有,所以,调用上下文会从属性直接注入。
步骤:
- 继承TransientRpcServer或者实现ITransientRpcServer接口。
public class MyRpcServer : TransientRpcServer
{
[Description("登录")]
[DmtpRpc]
public bool Login(string account,string password)
{
if ( this.CallContext.Caller is TcpDmtpSessionClient)
{
Console.WriteLine("TcpDmtpRpc请求");
}
if (account=="123"&&password=="abc")
{
return true;
}
return false;
}
}
//或使用泛型上下文
public class MyRpcServer : TransientRpcServer<IDmtpRpcCallContext>
{
[Description("登录")]
[DmtpRpc]
public bool Login(string account, string password)
{
if (this.CallContext.Caller is TcpDmtpSessionClient)
{
Console.WriteLine("TcpDmtpRpc请求");
}
if (account == "123" && password == "abc")
{
return true;
}
return false;
}
}
2.3 通过IRpcCallContextAccessor服务获取
不管在单例,还是瞬态的Rpc服务,都可以通过IRpcCallContextAccessor
服务获取到当前调用的上下文。
首先,需要在容器中注册IRpcCallContextAccessor
服务。
.ConfigureContainer(a =>
{
a.AddRpcCallContextAccessor();
});
然后,在Rpc服务中,通过IRpcCallContextAccessor
服务获取到当前调用的上下文。
public partial class MyRpcServer : RpcServer
{
private readonly IRpcCallContextAccessor m_rpcCallContextAccessor;
public MyRpcServer(IRpcCallContextAccessor rpcCallContextAccessor)
{
this.m_rpcCallContextAccessor = rpcCallContextAccessor;
}
[Description("测试从CallContextAccessor中获取当前关联的CallContext")]
[DmtpRpc]
public async Task TestGetCallContextFromCallContextAccessor()
{
//通过CallContextAccessor获取当前关联的CallContext
//此处即使m_rpcCallContextAccessor与当前RpcServer均为单例,也能获取到正确的CallContext
var callContext = this.m_rpcCallContextAccessor.CallContext;
await Task.CompletedTask;
}
}
该方式是通过AsyncLocal
的方式实现的,所以,即使把IRpcCallContextAccessor
保存到静态变量中,也能正常获取到正确的CallContext
。但前提是,获取流程在调用上下文生命周期内,否则,可能会获取到错误的CallContext
。
三、取消任务
一般的,Rpc在执行时,如遇到异常,或主动操作,会自动取消任务。所以我们在调用上下文ICallContext
接口中也规范了取消任务的方法。
下列将以DmtpRpc
为例,介绍如何取消任务。
/// <summary>
/// 测试取消调用
/// </summary>
/// <param name="callContext"></param>
/// <returns></returns>
[Description("测试取消调用")]
[DmtpRpc]
public async Task<int> TestCancellationToken(ICallContext callContext)
{
//模拟一个耗时操作
for (var i = 0; i < 10; i++)
{
//判断任务是否已被取消
if (callContext.Token.IsCancellationRequested)
{
Console.WriteLine("执行已取消");
return i;
}
Console.WriteLine($"执行{i}次");
await Task.Delay(1000);
}
return -1;
}
由于Rpc框架仅约束了取消任务的接口,并无实际实现。所以取消任务的时机,完全取决于具体Rpc框架的实现者。例如:对于DmtpRpc,当连接断开或主动取消任务时,均会触发取消任务。
同时,如果想自己手动取消任务,也可以在调用上下文生命周期内的任意地方,使用callContext.Cancel()
来取消任务。