You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用DispatchProxy实现带异步调用的缓存拦截是否可行?

使用DispatchProxy实现带异步调用的缓存拦截是否可行?

这个问题我之前做类似的拦截器时也碰到过,确实DispatchProxy的Invoke方法是同步签名,要在里面处理异步缓存操作会有点绕,咱们一步步捋清楚问题和解决思路。

首先得明确核心矛盾:你的Invoke方法是同步的,但里面调用的HybridCache.GetOrCreateAsync返回的是异步的ValueTask,如果强行在同步上下文里等待异步操作,很容易出现死锁或者无法正确获取结果的情况——尤其是如果被拦截的目标方法本身就是异步的(返回Task/ValueTask),这种强行同步等待的方式基本行不通。

可行的解决思路:区分同步/异步方法分别处理

正确的做法是不要一概而论,先判断被拦截的目标方法是同步还是异步,然后用不同的逻辑处理:

1. 处理异步目标方法(返回Task/ValueTask)

如果被拦截的方法本身就是异步的,那调用方本来就会通过await来获取结果,这时候你根本不需要在Invoke里等待异步缓存操作完成,而是把缓存操作的异步流程直接返回给调用方,让调用方的await去处理它。

修改你的Invoke方法,添加异步方法判断和处理逻辑:

protected override object? Invoke(MethodInfo? targetMethod, object?[]? args)
{
    Console.WriteLine("Before caching");

    if (targetMethod == null) return null;
    var returnType = targetMethod.ReturnType;

    // 情况1:目标方法是异步的(返回Task/ValueTask)
    if (typeof(Task).IsAssignableFrom(returnType))
    {
        // 生成唯一缓存键(不要用固定值,否则所有方法调用会共用缓存)
        var cacheKey = $"{targetMethod.DeclaringType?.FullName}.{targetMethod.Name}_{JsonSerializer.Serialize(args)}";

        // 执行缓存异步操作
        var cacheValueTask = _hybridCache.GetOrCreateAsync<object?>(cacheKey, async _ =>
        {
            var invokeResult = targetMethod.Invoke(_decorated, args);
            // 如果原方法返回的是Task,需要先await拿到真正的结果
            if (invokeResult is Task taskResult)
            {
                await taskResult.ConfigureAwait(false);
                // 提取泛型Task的Result值
                var resultProp = taskResult.GetType().GetProperty(nameof(Task<object>.Result));
                return resultProp?.GetValue(taskResult);
            }
            return invokeResult;
        });

        // 把ValueTask转换成目标方法需要的异步返回类型
        if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(Task<>))
        {
            // 处理Task<T>类型
            var resultType = returnType.GetGenericArguments()[0];
            var task = cacheValueTask.ContinueWith(t => 
                Convert.ChangeType(t.Result, resultType), 
                TaskContinuationOptions.OnlyOnRanToCompletion);
            return task;
        }
        else if (returnType == typeof(Task))
        {
            // 处理非泛型Task类型
            return cacheValueTask.AsTask();
        }
        else if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(ValueTask<>))
        {
            // 处理ValueTask<T>类型
            var resultType = returnType.GetGenericArguments()[0];
            var task = cacheValueTask.ContinueWith(t => 
                Convert.ChangeType(t.Result, resultType), 
                TaskContinuationOptions.OnlyOnRanToCompletion);
            return Activator.CreateInstance(returnType, task);
        }
        else if (returnType == typeof(ValueTask))
        {
            // 处理非泛型ValueTask类型
            return new ValueTask(cacheValueTask.AsTask());
        }
    }

    // 情况2:目标方法是同步的
    var syncCacheKey = $"{targetMethod.DeclaringType?.FullName}.{targetMethod.Name}_{JsonSerializer.Serialize(args)}";
    var syncResult = _hybridCache.GetOrCreateAsync<object?>(syncCacheKey, _ =>
    {
        var val = targetMethod.Invoke(_decorated, args);
        return ValueTask.FromResult(val);
    }).AsTask().ConfigureAwait(false).GetAwaiter().GetResult();

    return syncResult;
}

2. 处理同步目标方法

如果被拦截的方法是同步的,那可以用ConfigureAwait(false).GetAwaiter().GetResult()来同步等待异步缓存操作完成——注意一定要加ConfigureAwait(false),它可以避免捕获当前的同步上下文,减少死锁的概率,这可能是你之前尝试没成功的原因之一。

额外注意点

  • 缓存键不要用固定值:你现在代码里用的是固定的"CacheKey",这样所有方法调用都会共用同一个缓存值,肯定会出问题,要根据方法名、参数生成唯一的键(比如上面代码里用方法全名+参数序列化的方式)。
  • 避免死锁:如果你的代码运行在有同步上下文的环境(比如UI线程、ASP.NET Core之前的Web环境),强行同步等待异步操作很容易死锁,这也是为什么异步方法要传递异步流程而不是强行等待的原因。

整体来说,这种拦截方式是完全可行的,只要区分同步和异步方法分别处理,就可以避开同步/异步上下文的冲突问题。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.07 11:49:34