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

如何用.NET Emit为运行时的异步无返回Action添加Try-Catch异常捕获?

解决PubSub中Async Void处理程序的异常捕获问题

问题本质

你遇到的核心问题是async void方法的异常无法被常规try-catch捕获——这类方法抛出的异步异常会直接冒泡到当前同步上下文(比如ASP.NET的请求上下文),甚至导致进程崩溃,而且很难追踪。要解决这个问题,核心思路是把每个async void的Action<T>包装在带异常捕获的逻辑里,不管是编译时静态包装,还是运行时动态生成包装方法。


方案一:静态委托包装(无需Emit,推荐)

这是最简单的实现方式,不需要涉及IL生成,直接用C#的委托特性包装原Action:

实现代码

public static class PubSubErrorHandlingExtensions
{
    // 替换成你实际的日志实例(比如通过DI注入)
    private static readonly ILogger _logger = LoggerFactory.Create(builder => builder.AddConsole()).CreateLogger("PubSub");

    public static Action<T> WithErrorHandling<T>(this Action<T> originalAction)
    {
        return async arg =>
        {
            try
            {
                // 把原Action<T>转成Func<T, Task>,从而可以await捕获异步异常
                await ((Func<T, Task>)(async x => originalAction(x)))(arg);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "PubSub事件处理失败,主题:{Topic}", typeof(T).Name);
            }
        };
    }
}

使用方式

订阅时直接调用扩展方法包装你的处理程序:

SubscriberService.Subscribe(new SubscribeRequest<string>
{  
   Topic = TopicName,
   Action = (async pubSubEvent => await DoSomthingAsync()).WithErrorHandling()
});

这个方案的优势是:代码简洁易维护,不需要了解IL指令,同时能完整捕获异步异常。


方案二:用Emit动态生成包装方法

如果必须在运行时动态生成带try-catch的方法(比如需要批量处理大量不同类型的Action),可以用ILGenerator实现。不过要注意:async void的异步异常需要通过await Task的方式捕获,所以动态生成的方法内部要把原Action转成可await的Task。

实现代码

using System.Reflection;
using System.Reflection.Emit;

public static class DynamicPubSubWrapper
{
    private static readonly ILogger _logger = LoggerFactory.Create(builder => builder.AddConsole()).CreateLogger("DynamicPubSub");

    public static Action<T> CreateWrappedAction<T>(Action<T> originalAction)
    {
        // 1. 创建动态程序集、模块和类型
        var assemblyName = new AssemblyName($"DynamicPubSub_{Guid.NewGuid()}");
        var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
        var moduleBuilder = assemblyBuilder.DefineDynamicModule("WrapperModule");
        var typeBuilder = moduleBuilder.DefineType($"ActionWrapper_{typeof(T).FullName}", TypeAttributes.Public | TypeAttributes.Sealed);

        // 2. 定义包装方法:静态,参数为T,返回void
        var methodBuilder = typeBuilder.DefineMethod(
            "HandleEvent",
            MethodAttributes.Public | MethodAttributes.Static,
            typeof(void),
            new[] { typeof(T) });

        var il = methodBuilder.GetILGenerator();
        var exceptionType = typeof(Exception);
        var logMethod = typeof(ILogger).GetMethod("LogError", new[] { exceptionType, typeof(string) });

        // 3. 构建try-catch块
        var tryBlock = il.BeginExceptionBlock();

        // --- Try块逻辑:调用原Action并等待异步完成 ---
        // 加载方法参数(T类型的事件对象)
        il.Emit(OpCodes.Ldarg_0);
        // 创建Func<T, Task>委托,包装原Action的调用(支持异步)
        il.Emit(OpCodes.Ldftn, originalAction.Method);
        il.Emit(OpCodes.Newobj, typeof(Func<T, Task>).GetConstructor(new[] { typeof(object), typeof(IntPtr) }));
        // 调用Func的Invoke方法,得到Task
        il.Emit(OpCodes.Callvirt, typeof(Func<T, Task>).GetMethod("Invoke"));
        // 等待Task完成(同步等待,若需非阻塞异步需生成async状态机,逻辑更复杂)
        il.Emit(OpCodes.Call, typeof(Task).GetMethod("Wait"));

        // 离开try块
        il.Emit(OpCodes.Leave_S, tryBlock);

        // --- Catch块逻辑:捕获异常并日志 ---
        il.BeginCatchBlock(exceptionType);
        // 加载日志实例
        il.Emit(OpCodes.Ldsfld, typeof(DynamicPubSubWrapper).GetField("_logger", BindingFlags.NonPublic | BindingFlags.Static));
        // 加载异常对象(catch块的异常在栈顶)
        il.Emit(OpCodes.Ldarg_0);
        // 加载日志消息
        il.Emit(OpCodes.Ldstr, "动态包装的PubSub事件处理失败");
        // 调用LogError方法
        il.Emit(OpCodes.Callvirt, logMethod);

        // 结束异常块
        il.EndExceptionBlock();

        // 返回
        il.Emit(OpCodes.Ret);

        // 4. 创建类型并生成委托
        var wrapperType = typeBuilder.CreateType();
        var wrappedMethod = wrapperType.GetMethod("HandleEvent");
        return (Action<T>)Delegate.CreateDelegate(typeof(Action<T>), wrappedMethod);
    }
}

使用方式

var wrappedAction = DynamicPubSubWrapper.CreateWrappedAction<string>(async pubSubEvent => await DoSomthingAsync());
SubscriberService.Subscribe(new SubscribeRequest<string>
{  
   Topic = TopicName,
   Action = wrappedAction
});

注意事项

  • Emit的代码对IL指令细节要求很高,参数加载、方法调用的指令顺序不能出错;
  • 异步方法的状态机生成逻辑复杂,上面的代码用Task.Wait()做了同步等待简化,若需真正的异步非阻塞,需要生成完整的async方法状态机,会大幅增加代码复杂度;
  • 动态生成的程序集会留在内存中,大量使用可能导致内存占用过高,建议缓存生成的委托。

内容的提问来源于stack exchange,提问作者nive-2510

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 17:45:49