共计 3058 个字符,预计需要花费 8 分钟才能阅读完成。
从智能客服看函数调用挑战
最近在帮某金融公司改造智能客服系统时遇到典型场景:当用户输入 ” 查询最近三个月交易 ” 时,需要动态调用 TransactionService.Query(DateTime start, DateTime end) 方法。传统反射方案在每秒 3000+ 请求下出现明显瓶颈:

- 单次反射调用耗时约 1.2ms(而直接调用仅 0.02ms)
- GC 压力导致第 95 百分位延迟突破 200ms
- 缺乏对异步方法的友好支持
技术方案选型对比
用 BenchmarkDotNet 测试三种核心方案(测试机:8 核 i7-1165G7):
[MemoryDiagnoser]
public class FunctionInvokeBenchmark
{
private readonly MethodInfo _reflectMethod;
private readonly DynamicMethod _dynamicMethod;
private readonly Func<object, object[], object> _expressionCompiled;
[Benchmark(Baseline = true)]
public void ReflectionInvoke() => _reflectMethod.Invoke(null, new object[] {"test"});
[Benchmark]
public void DynamicMethodInvoke() => _dynamicMethod.Invoke(null, new object[] {"test"});
[Benchmark]
public void ExpressionCompiled() => _expressionCompiled(null, new object[] {"test"});
}
测试结果:
| 方法 | 均值 | 分配内存 |
|---|---|---|
| ReflectionInvoke | 1.15 μs | 104 B |
| DynamicMethodInvoke | 0.32 μs | 32 B |
| ExpressionCompiled | 0.08 μs | 0 B |
表达式树方案深度实现
1. 构建类型安全映射
关键是通过泛型约束建立强类型契约:
public class FunctionDescriptor<TDelegate> where TDelegate : Delegate
{private Lazy<Func<object[], object>> _compiledFunc;
public FunctionDescriptor(TDelegate method)
{_compiledFunc = new Lazy<Func<object[], object>>(() =>
BuildExpression(method.Method));
}
private static Func<object[], object> BuildExpression(MethodInfo method)
{var parameters = Expression.Parameter(typeof(object[]), "args");
// 参数类型转换和校验逻辑...
var call = Expression.Call(method, argExpressions);
return Expression.Lambda<Func<object[], object>>(Expression.Convert(call, typeof(object)), parameters).Compile();}
}
2. 异步方法特殊处理
针对 Task<T> 需要解包处理:
if (method.ReturnType.IsGenericType &&
method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>))
{var resultType = method.ReturnType.GetGenericArguments()[0];
var awaitExpr = Expression.Call(typeof(TaskExtensions), "Unwrap",
new[] { resultType}, callExpr);
callExpr = Expression.Convert(awaitExpr, typeof(object));
}
生产级代码示例
完整路由表实现:
public class FunctionRouter
{private readonly ConcurrentDictionary<string, Func<object[], object>> _routes =
new ConcurrentDictionary<string, Func<object[], object>>();
public void Register(string name, Delegate method)
{var compiled = ExpressionCompiler.Compile(method);
_routes.TryAdd(name, (args) =>
{
try {return compiled(args);
}
catch (Exception ex) {throw new FunctionInvokeException(name, ex);
}
});
}
public async Task<object> InvokeAsync(string functionName, object[] args)
{if (!_routes.TryGetValue(functionName, out var func))
throw new FunctionNotFoundException(functionName);
var result = func(args);
if (result is Task taskResult)
await taskResult.ConfigureAwait(false);
return result is Task<object> taskObj ?
await taskObj : result;
}
}
生产环境陷阱规避
1. 内存泄漏排查
动态程序集需特别注意:
// 错误示例:每次调用都生成新程序集
var assembly = AssemblyBuilder.DefineDynamicAssembly(...);
// 正确做法:使用静态缓存
private static readonly ConcurrentDictionary<MethodInfo, Func<object[], object>>
_cache = new ConcurrentDictionary<MethodInfo, Func<object[], object>>();
2. 熔断策略配置
基于 Polly 的弹性策略:
var circuitBreaker = Policy
.Handle<FunctionInvokeException>()
.CircuitBreakerAsync(
exceptionsAllowedBeforeBreaking: 3,
durationOfBreak: TimeSpan.FromSeconds(30)
);
3. 版本兼容方案
采用语义化版本路由:
Register("v1/queryTransactions", QueryV1);
Register("v2/queryTransactions", QueryV2);
开放性问题
当项目需要 Native AOT 编译时,动态表达式树编译会面临挑战。可能的平衡方案:
- 预生成所有可能的调用路径
- 采用基于接口的静态代理
- 限制动态性范围,关键路径保持静态
您在实际项目中如何解决这个问题?欢迎在评论区分享经验。
正文完
