服务间的通讯-WebApiClient

声明式REST Api客户端能够帮助我们在日常开发中减少很多构建HttpClient处理请求的工作,减少对业务代码的侵入。前文介绍了工作中自己实现的REST Api代理,其实.net 体系也有一个比较好用的开源REST Client,是大大老九写的 WebApiClient。

比较了一下,自己实现的REST Api客户端和WebApiClient在使用上基本类似,不过WebApiClient确实要强大很多,考虑了更多情况的兼容和更好的扩展性。这里简单的介绍一下WebApiClient在.net core中的使用,再介绍下怎么和nacos结合做微服务间的api调用,并且解析一下WebApiClient的核心实现源码。

WebApiClient的基本使用

  1. 安装WebApiClient nuget包
    WebApiClient有以前兼容.net framework和.net core的版本WebApiClient-JIT和现在.net core版本WebApiClientCore,.net Core下请安装 WebApiClientCore包

    Install-Package WebApiClientCore
    
  2. 定义代理接口

    [HttpHost("http://localhost:5000")]
    public interface IService
    {
        [HttpGet("/WeatherForecast")]
        public ITask<string> GetAsync();
    }
    
  3. 在startup类中进行注入

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddHttpApi<IService>();
    }
    

这里通过HttpHost特性配置了同一个域名,当前代理接口下的方法只配置路由,则所有的方法都会请求这个域名下的地址。如果方法上的特性重新指定一个完整的地址,则以方法上特性指定的优先。

除了在代理接口上通过特性的方式标记接口地址之外,还可以在依赖注入的时候配置。

services.AddHttpApi<IService>(options => options.HttpHost = new Uri("http://localhost:5000"));

public interface IService
{
    [HttpGet("/WeatherForecast")]
    public ITask<string> GetAsync();
}

api请求结果有多种返回方式,如上面的代理接口中是以string的方式接收返回结果,当然我们大多数情况下还是以强类型的方式接收的,如下:

[HttpHost("http://localhost:5000")]
public interface IService
{
    [HttpGet("/WeatherForecast")]
    public ITask<string> GetAsync();


    [HttpGet("/WeatherForecast")]
    public ITask<IEnumerable<WeatherForecast>> Get2Async();
}

[HttpGet("Get")]
 public async Task<IEnumerable<WeatherForecast>> RemoteGet()
 {
     var result = await _service.Get2Async();
     return result;
 }

WebApiClient提供了多种对请求参数,返回值进行处理或者校验的方式,这里就不细讲了,具体的可以参考老九大大的文章或者github上的文档。

结合nacos实现服务接口调用

由于微服务间的接口调用一般不经过网关,而是在服务内部自由调用,在一个服务有多个实例的情况下,往往会有负载均衡策略,所以微服务间通讯时Host地址往往是不固定的,需要在接口调用之前动态地去设置。

WebApiClient提供了动态Host的处理方式,可以在在请求还没有发出去之前的任何环节,对ApiRequestContext进行拦截,修改请求消息的RequestUri来实现动态目标。其中更是提供了ApiActionAttribute、ApiFilterAttribute等特性,大大方便了我们对动态host的实现。

首先定义一个ServiceNameAttribute继承ApiActionAttribute特性,在请求发送前进行拦截

public class ServiceNameAttribute : ApiActionAttribute
{
    public ServiceNameAttribute(string name)
    {
        Name = name;
        OrderIndex = int.MinValue;
    }

    public string Name { get; set; }

    public override async Task OnRequestAsync(ApiRequestContext context)
    {
        IServiceProvider sp = context.HttpContext.ServiceProvider;
        IHostProvider hostProvider = sp.GetRequiredService<IHostProvider>();
        //服务名也可以在接口配置时挂在Properties中
        string host = await hostProvider.ResolveService(this.Name);
        HttpApiRequestMessage requestMessage = context.HttpContext.RequestMessage;
        // 覆盖了RequestUri,完成了替换
        requestMessage.RequestUri = requestMessage.MakeRequestUri(new Uri(host));
    }
}

这里的IHostProvider是解析服务名称的接口,可以根据不同的服务注册中心提供实现,例如nacos如下:

public class NacosHostProvider : IHostProvider
{
    private readonly INacosNamingService _nacosNamingService;
    public NacosHostProvider(INacosNamingService nacosNamingService)
    {
        _nacosNamingService = nacosNamingService;
    }

    public async Task<string> ResolveService(string name)
    {
        var instance = await _nacosNamingService.SelectOneHealthyInstance(name);
        if (instance != null)
        {
            var host = $"{instance.Ip}:{instance.Port}";

            var baseUrl = instance.Metadata.TryGetValue("secure", out _)
                ? $"https://{host}"
                : $"http://{host}";
            return baseUrl;
        }
        throw new Exception($"{name}服务不可用!");
    }
}

之后提供依赖注入注册的方式

public static class NacosProxyServiceCollectionExtensions
{
    /// <summary>
    /// 添加 Nacos Http 请求代理必要依赖
    /// </summary>
    /// <param name="services"></param>
    /// <returns></returns>
    public static IServiceCollection AddNacosHttpProxy(this IServiceCollection services)
    {
        var configuration = services.GetConfiguration();
        services.AddNacosAspNet(configuration, "nacos");
        services.AddSingleton<IHostProvider, NacosHostProvider>();
        return services;
    }
}

最后是使用,和WebApiClient正常使用没有太大区别,但是要注意不用在设置HttpHost,而是需要设置ServiceName特性,HttpHost特性的设置都会被服务发现获取到的地址替换掉。

[ServiceName("App1")]
public interface IService
{
	[HttpGet("/Service")]
	public ITask<string> GetAsync();
}


public async Task Should_Get_Response()
{
    var serviceCollection = new ServiceCollection();

    var builder = new ConfigurationBuilder();
    builder.AddJsonFile("appsettings.json");
    var configuration = builder.Build();
    serviceCollection.AddSingleton<IConfiguration>(configuration);

    serviceCollection.AddNacosHttpProxy();
    serviceCollection.AddHttpApi<IService>();
    var serviceProvider = serviceCollection.BuildServiceProvider();

    var service = serviceProvider.GetRequiredService<IService>();
    var host = await service.GetAsync();
    Assert.Equal("http://172.20.64.1:5000", host);
}

WebApiClient核心实现源码研究

WebApiClient的核心在于接口代理类的动态实现,这一块的代码在于DefaultHttpApiActivator<THttpApi>中,通过下面的源码可以看到,这里是通过反射动态编译一个类实现THttpApi,也就是我们定义的代理接口的。

/// <summary>
/// 运行时使用Emit动态创建THttpApi的代理类和代理类实例
/// </summary>
/// <typeparam name="THttpApi"></typeparam>
public class DefaultHttpApiActivator<THttpApi> : HttpApiActivator<THttpApi>
{
    /// <summary>
    /// IHttpApiInterceptor的Intercept方法
    /// </summary>
    private static readonly MethodInfo interceptMethod = typeof(IHttpApiInterceptor).GetMethod(nameof(IHttpApiInterceptor.Intercept)) ?? throw new MissingMethodException(nameof(IHttpApiInterceptor.Intercept));

    /// <summary>
    /// 代理类型的构造器的参数类型
    /// (IHttpApiInterceptor interceptor,ApiActionInvoker[] actionInvokers)
    /// </summary>
    private static readonly Type[] proxyTypeCtorArgTypes = new Type[] { typeof(IHttpApiInterceptor), typeof(ApiActionInvoker[]) };


    /// <summary>
    /// 运行时使用Emit动态创建THttpApi的代理类和代理类实例
    /// </summary>
    /// <param name="apiActionDescriptorProvider"></param>
    /// <param name="actionInvokerProvider"></param>
    /// <exception cref="ArgumentException"></exception>
    /// <exception cref="NotSupportedException"></exception>
    public DefaultHttpApiActivator(IApiActionDescriptorProvider apiActionDescriptorProvider, IApiActionInvokerProvider actionInvokerProvider)
        : base(apiActionDescriptorProvider, actionInvokerProvider)
    {
    }

    /// <summary>
    /// 创建实例工厂
    /// </summary>
    /// <exception cref="NotSupportedException"></exception>
    /// <exception cref="ProxyTypeCreateException"></exception>
    /// <returns></returns>
    protected override Func<IHttpApiInterceptor, ApiActionInvoker[], THttpApi> CreateFactory()
    {
        var proxyType = BuildProxyType(typeof(THttpApi), this.ApiMethods);
        return LambdaUtil.CreateCtorFunc<IHttpApiInterceptor, ApiActionInvoker[], THttpApi>(proxyType);
    }

    /// <summary>
    /// 创建IHttpApi代理类的类型
    /// </summary>
    /// <param name="interfaceType">接口类型</param>
    /// <param name="apiMethods">接口的方法</param>
    /// <exception cref="NotSupportedException"></exception>
    /// <exception cref="ProxyTypeCreateException"></exception>
    /// <returns></returns>
    private static Type BuildProxyType(Type interfaceType, MethodInfo[] apiMethods)
    {
        // 接口的实现在动态程序集里,所以接口必须为public修饰才可以创建代理类并实现此接口            
        if (interfaceType.IsVisible == false)
        {
            var message = Resx.required_PublicInterface.Format(interfaceType);
            throw new NotSupportedException(message);
        }

        var moduleName = Guid.NewGuid().ToString();
        var assemblyName = new AssemblyName(Guid.NewGuid().ToString());

        var module = AssemblyBuilder
            .DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run)
            .DefineDynamicModule(moduleName);

        var typeName = interfaceType.FullName ?? Guid.NewGuid().ToString();
        var builder = module.DefineType(typeName, System.Reflection.TypeAttributes.Class);
        builder.AddInterfaceImplementation(interfaceType);

        var fieldApiInterceptor = BuildField(builder, "<>apiInterceptor", typeof(IHttpApiInterceptor));
        var fieldActionInvokers = BuildField(builder, "<>actionInvokers", typeof(ApiActionInvoker[]));

        BuildCtor(builder, fieldApiInterceptor, fieldActionInvokers);
        BuildMethods(builder, apiMethods, fieldApiInterceptor, fieldActionInvokers);

        var proxyType = builder.CreateType();
        return proxyType ?? throw new ProxyTypeCreateException(interfaceType);
    }

    /// <summary>
    /// 生成代理类型的字段
    /// </summary>
    /// <param name="builder">类型生成器</param>
    /// <param name="fieldName">字段名称</param>
    /// <param name="fieldType">字段类型</param>
    /// <returns></returns>
    private static FieldBuilder BuildField(TypeBuilder builder, string fieldName, Type fieldType)
    {
        const FieldAttributes filedAttribute = FieldAttributes.Private | FieldAttributes.InitOnly;
        return builder.DefineField(fieldName, fieldType, filedAttribute);
    }

    /// <summary>
    /// 生成代理类型的构造器
    /// </summary>
    /// <param name="builder">类型生成器</param>
    /// <param name="fieldApiInterceptor">拦截器字段</param>
    /// <param name="fieldActionInvokers">action执行器字段</param> 
    /// <returns></returns>
    private static void BuildCtor(TypeBuilder builder, FieldBuilder fieldApiInterceptor, FieldBuilder fieldActionInvokers)
    {
        // .ctor(IHttpApiInterceptor apiInterceptor, ApiActionInvoker[] actionInvokers)
        var ctor = builder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, proxyTypeCtorArgTypes);

        var il = ctor.GetILGenerator();

        // this.apiInterceptor = 第一个参数
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ldarg_1);
        il.Emit(OpCodes.Stfld, fieldApiInterceptor);

        // this.actionInvokers = 第二个参数
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ldarg_2);
        il.Emit(OpCodes.Stfld, fieldActionInvokers);

        il.Emit(OpCodes.Ret);
    }

    /// <summary>
    /// 生成代理类型的接口实现方法
    /// </summary>
    /// <param name="builder">类型生成器</param>
    /// <param name="actionMethods">接口的方法</param>
    /// <param name="fieldApiInterceptor">拦截器字段</param>
    /// <param name="fieldActionInvokers">action执行器字段</param> 
    private static void BuildMethods(TypeBuilder builder, MethodInfo[] actionMethods, FieldBuilder fieldApiInterceptor, FieldBuilder fieldActionInvokers)
    {
        const MethodAttributes implementAttribute = MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.Final | MethodAttributes.NewSlot | MethodAttributes.HideBySig;

        for (var i = 0; i < actionMethods.Length; i++)
        {
            var actionMethod = actionMethods[i];
            var actionParameters = actionMethod.GetParameters();
            var parameterTypes = actionParameters.Select(p => p.ParameterType).ToArray();

            var iL = builder
                .DefineMethod(actionMethod.Name, implementAttribute, CallingConventions.Standard, actionMethod.ReturnType, parameterTypes)
                .GetILGenerator();

            // this.apiInterceptor
            iL.Emit(OpCodes.Ldarg_0);
            iL.Emit(OpCodes.Ldfld, fieldApiInterceptor);

            // this.actionInvokers[i]
            iL.Emit(OpCodes.Ldarg_0);
            iL.Emit(OpCodes.Ldfld, fieldActionInvokers);
            iL.Emit(OpCodes.Ldc_I4, i);
            iL.Emit(OpCodes.Ldelem_Ref);

            // var arguments = new object[parameters.Length]
            var arguments = iL.DeclareLocal(typeof(object[]));
            iL.Emit(OpCodes.Ldc_I4, actionParameters.Length);
            iL.Emit(OpCodes.Newarr, typeof(object));
            iL.Emit(OpCodes.Stloc, arguments);

            for (var j = 0; j < actionParameters.Length; j++)
            {
                iL.Emit(OpCodes.Ldloc, arguments);
                iL.Emit(OpCodes.Ldc_I4, j);
                iL.Emit(OpCodes.Ldarg, j + 1);

                var parameterType = parameterTypes[j];
                if (parameterType.IsValueType || parameterType.IsGenericParameter)
                {
                    iL.Emit(OpCodes.Box, parameterType);
                }
                iL.Emit(OpCodes.Stelem_Ref);
            }

            // 加载arguments参数
            iL.Emit(OpCodes.Ldloc, arguments);

            // Intercep(actionInvoker, arguments)
            iL.Emit(OpCodes.Callvirt, interceptMethod);

            if (actionMethod.ReturnType == typeof(void))
            {
                iL.Emit(OpCodes.Pop);
            }

            iL.Emit(OpCodes.Castclass, actionMethod.ReturnType);
            iL.Emit(OpCodes.Ret);
        }
    }
}

之后通过Lambda表达式动态创建了THttpApi实现类的构造函数委托,再在通过AddHttpApi<THttpApi>()向容器注册的时候,调用HttpApiProvider<THttpApi>的CreateHttpApi方法,创建THttpApi动态实现类。

/// <summary>
/// 添加HttpApi代理类到服务
/// </summary>
/// <typeparam name="THttpApi"></typeparam>
/// <param name="services"></param>
/// <returns></returns>
public static IHttpClientBuilder AddHttpApi<THttpApi>(this IServiceCollection services) where THttpApi : class
{
    var name = HttpApi.GetName(typeof(THttpApi));

    services.AddWebApiClient();
    services.NamedHttpApiType(name, typeof(THttpApi));
    services.TryAddSingleton(typeof(HttpApiProvider<>));
    services.TryAddTransient(serviceProvider =>
    {
        var httiApiProvider = serviceProvider.GetRequiredService<HttpApiProvider<THttpApi>>();
        return httiApiProvider.CreateHttpApi(serviceProvider, name);
    });

    return services.AddHttpClient(name);
}

而最终,具体的Api的调用执行是通过IHttpApiInterceptor实现类中的Intercept方法来实现的。

DefaultApiActionInvoker<TResult>中可以看到,Invoke方法中又是通过静态类ApiRequestExecuter对请求上下文和响应上下文进行处理,包括参数校验、过滤器执行等,通过静态类ApiRequestSender进行最终的Api的调用,最终也是通过HttpClient进行调用Api接口的。

/// <summary>
/// 执行Api方法
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
private async Task<TResult> InvokeAsync(ApiRequestContext request)
{
#nullable disable
    var response = await ApiRequestExecuter.ExecuteAsync(request).ConfigureAwait(false);
    if (response.ResultStatus == ResultStatus.HasResult)
    {
        return (TResult)response.Result;
    }

    if (response.ResultStatus == ResultStatus.HasException)
    {
        ExceptionDispatchInfo.Capture(response.Exception).Throw();
    }

    throw new ApiReturnNotSupportedExteption(response);
#nullable enable
}
static class ApiRequestExecuter
{
    /// <summary>
    /// 执行上下文
    /// </summary>
    /// <param name="request">请求上下文</param>
    /// <returns></returns>
    public static async Task<ApiResponseContext> ExecuteAsync(ApiRequestContext request)
    {
        await HandleRequestAsync(request).ConfigureAwait(false);
        var response = await ApiRequestSender.SendAsync(request).ConfigureAwait(false);
        await HandleResponseAsync(response).ConfigureAwait(false);
        return response;
    }

    /// <summary>
    /// 处理请求上下文
    /// </summary>
    /// <returns></returns>
    private static async Task HandleRequestAsync(ApiRequestContext context)
    {
        // 参数验证
        var validateProperty = context.HttpContext.HttpApiOptions.UseParameterPropertyValidate;
        foreach (var parameter in context.ActionDescriptor.Parameters)
        {
            var parameterValue = context.Arguments[parameter.Index];
            DataValidator.ValidateParameter(parameter, parameterValue, validateProperty);
        }

        // action特性请求前执行
        foreach (var attr in context.ActionDescriptor.Attributes)
        {
            await attr.OnRequestAsync(context).ConfigureAwait(false);
        }

        // 参数特性请求前执行
        foreach (var parameter in context.ActionDescriptor.Parameters)
        {
            var ctx = new ApiParameterContext(context, parameter);
            foreach (var attr in parameter.Attributes)
            {
                await attr.OnRequestAsync(ctx).ConfigureAwait(false);
            }
        }

        // Return特性请求前执行
        foreach (var @return in context.ActionDescriptor.Return.Attributes)
        {
            await @return.OnRequestAsync(context).ConfigureAwait(false);
        }

        // GlobalFilter请求前执行 
        foreach (var filter in context.HttpContext.HttpApiOptions.GlobalFilters)
        {
            await filter.OnRequestAsync(context).ConfigureAwait(false);
        }

        // Filter请求前执行 
        foreach (var filter in context.ActionDescriptor.FilterAttributes)
        {
            await filter.OnRequestAsync(context).ConfigureAwait(false);
        }
    }

    /// <summary>
    /// 处理响应上下文
    /// </summary>
    /// <returns></returns>
    private static async Task HandleResponseAsync(ApiResponseContext context)
    {
        // Return特性请求后执行
        var returns = context.ActionDescriptor.Return.Attributes.GetEnumerator();
        while (context.ResultStatus == ResultStatus.None && returns.MoveNext())
        {
            try
            {
                await returns.Current.OnResponseAsync(context).ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                context.Exception = ex;
            }
        }

        // 结果验证
        if (context.ResultStatus == ResultStatus.HasResult &&
            context.ActionDescriptor.Return.DataType.IsRawType == false &&
            context.HttpContext.HttpApiOptions.UseReturnValuePropertyValidate)
        {
            try
            {
                DataValidator.ValidateReturnValue(context.Result);
            }
            catch (Exception ex)
            {
                context.Exception = ex;
            }
        }

        // GlobalFilter请求后执行 
        foreach (var filter in context.HttpContext.HttpApiOptions.GlobalFilters)
        {
            await filter.OnResponseAsync(context).ConfigureAwait(false);
        }

        // Filter请求后执行
        foreach (var filter in context.ActionDescriptor.FilterAttributes)
        {
            await filter.OnResponseAsync(context).ConfigureAwait(false);
        }
    }
}
/// <summary>
/// 提供http请求
/// </summary>
static class ApiRequestSender
{
   /// <summary>
   /// 发送http请求
   /// </summary>
   /// <param name="context"></param>
   /// <exception cref="ApiInvalidConfigException"></exception>
   /// <returns></returns>
   public static async Task<ApiResponseContext> SendAsync(ApiRequestContext context)
   {
       if (context.HttpContext.RequestMessage.RequestUri == null)
       {
           throw new ApiInvalidConfigException(Resx.required_HttpHost);
       }

       try
       {
           await SendCoreAsync(context).ConfigureAwait(false);
           return new ApiResponseContext(context);
       }
       catch (Exception ex)
       {
           return new ApiResponseContext(context) { Exception = ex };
       }
   }

   /// <summary>
   /// 发送http请求
   /// </summary>
   /// <param name="context"></param>
   /// <exception cref="HttpRequestException"></exception> 
   /// <returns></returns>
   private static async Task SendCoreAsync(ApiRequestContext context)
   {
       var actionCache = await context.GetCaheAsync().ConfigureAwait(false);
       if (actionCache != null && actionCache.Value != null)
       {
           context.HttpContext.ResponseMessage = actionCache.Value;
       }
       else
       {
           var client = context.HttpContext.HttpClient;
           var request = context.HttpContext.RequestMessage;
           var completionOption = context.GetCompletionOption();

           using var tokenLinker = new CancellationTokenLinker(context.HttpContext.CancellationTokens);
           var response = await client.SendAsync(request, completionOption, tokenLinker.Token).ConfigureAwait(false);

           context.HttpContext.ResponseMessage = response;
           await context.SetCacheAsync(actionCache?.Key, response).ConfigureAwait(false);
       }
   }

   /// <summary>
   /// 获取响应的缓存
   /// </summary>
   /// <param name="context"></param>
   /// <returns></returns>
   private static async Task<ActionCache?> GetCaheAsync(this ApiRequestContext context)
   {
       var attribute = context.ActionDescriptor.CacheAttribute;
       if (attribute == null)
       {
           return default;
       }

       if (attribute.GetReadPolicy(context) == CachePolicy.Ignore)
       {
           return default;
       }

       var cacheKey = await attribute.GetCacheKeyAsync(context).ConfigureAwait(false);
       if (string.IsNullOrEmpty(cacheKey))
       {
           return default;
       }

       var provider = attribute.GetCacheProvider(context);
       if (provider == null)
       {
           return default;
       }

       var responseCache = await provider.GetAsync(cacheKey).ConfigureAwait(false);
       if (responseCache.HasValue == false || responseCache.Value == null)
       {
           return new ActionCache(cacheKey, null);
       }

       var request = context.HttpContext.RequestMessage;
       var response = responseCache.Value.ToResponseMessage(request, provider.Name);
       return new ActionCache(cacheKey, response);
   }

   /// <summary>
   /// 更新响应到缓存
   /// </summary>
   /// <param name="context"></param>
   /// <param name="cacheKey">缓存键</param>
   /// <param name="response">响应消息</param>
   /// <returns></returns>
   private static async Task SetCacheAsync(this ApiRequestContext context, string? cacheKey, HttpResponseMessage? response)
   {
       var attribute = context.ActionDescriptor.CacheAttribute;
       if (attribute == null)
       {
           return;
       }

       if (response == null)
       {
           return;
       }

       if (attribute.GetWritePolicy(context) == CachePolicy.Ignore)
       {
           return;
       }

       if (string.IsNullOrEmpty(cacheKey) == true)
       {
           cacheKey = await attribute.GetCacheKeyAsync(context).ConfigureAwait(false);
       }

       if (cacheKey == null)
       {
           return;
       }

       var provider = attribute.GetCacheProvider(context);
       if (provider == null)
       {
           return;
       }

       var cacheEntry = await ResponseCacheEntry.FromResponseMessageAsync(response).ConfigureAwait(false);
       await provider.SetAsync(cacheKey, cacheEntry, attribute.Expiration).ConfigureAwait(false);
   }


   /// <summary>
   /// 表示Action缓存结果
   /// </summary>
   private class ActionCache
   {
       /// <summary>
       /// 获取缓存的键
       /// </summary>
       public string Key { get; }

       /// <summary>
       /// 获取响应信息
       /// </summary>
       public HttpResponseMessage? Value { get; set; }

       /// <summary>
       /// 缓存结果
       /// </summary>
       /// <param name="key">缓存的键</param>
       /// <param name="value">响应信息</param>
       public ActionCache(string key, HttpResponseMessage? value)
       {
           this.Key = key;
           this.Value = value;
       }
   }

   /// <summary>
   /// 表示CancellationToken链接器
   /// </summary>
   private struct CancellationTokenLinker : IDisposable
   {
       /// <summary>
       /// 链接产生的tokenSource
       /// </summary>
       private readonly CancellationTokenSource? tokenSource;

       /// <summary>
       /// 获取token
       /// </summary>
       public CancellationToken Token { get; }

       /// <summary>
       /// CancellationToken链接器
       /// </summary>
       /// <param name="tokenList"></param>
       public CancellationTokenLinker(IList<CancellationToken> tokenList)
       {
           if (IsNoneCancellationToken(tokenList) == true)
           {
               this.tokenSource = null;
               this.Token = CancellationToken.None;
           }
           else
           {
               this.tokenSource = CancellationTokenSource.CreateLinkedTokenSource(tokenList.ToArray());
               this.Token = this.tokenSource.Token;
           }
       }

       /// <summary>
       /// 是否为None的CancellationToken
       /// </summary>
       /// <param name="tokenList"></param>
       /// <returns></returns>
       private static bool IsNoneCancellationToken(IList<CancellationToken> tokenList)
       {
           if (tokenList.Count == 0)
           {
               return true;
           }
           if (tokenList.Count == 1 && tokenList[0] == CancellationToken.None)
           {
               return true;
           }
           return false;
       }

       /// <summary>
       /// 释放资源
       /// </summary>
       public void Dispose()
       {
           if (this.tokenSource != null)
           {
               this.tokenSource.Dispose();
           }
       }
   }
}

微服务系列文章:
上一篇: 服务间的通讯—工作实践
下一篇:配置中心—nacos配置中心

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值