c# 从delegate.begininvoke 捕获异常而不调用delegate.endinvoke

Posted

技术标签:

【中文标题】c# 从delegate.begininvoke 捕获异常而不调用delegate.endinvoke【英文标题】:c# catch an exception from delegate.begininvoke without calling delegate.endinvoke 【发布时间】:2020-05-06 20:12:14 【问题描述】:

我有一个监控一个数据库(或多个数据库)的程序。 为此,我构建了一个包含有关如何监视数据库的所有信息的类。 该类包含一个委托,该委托指向一个监视数据库并相应更改状态字段的函数。

主线程创建一个新的类实例并调用class.delegate.begininvoke()。 主线程循环检查每个创建的类的状态,并在发生任何更改时通知用户。

一个简单的代码示例:

Class Monitor

    private Object lockObj;
    private delegate void MonitorHandlerDelegate();
    private MonitorHandlerDelegate mainHandler;

    private int State;
    private int DBid;

    public Monitor(int DBid)
    
        this.DBid = DBid;
        mainHandler += MonitorHandler;
        lockObj = new Object();
    

    private void MonitorHandler()
    
        do
        
            state = CheckDB(DBid); // 0 is ok, 1 is fail, 2 is InWork, 3 is stop monitoring
         while (state != 3);
    

    public int state
    
        get  lock(lockObj)  return State; 
        set  lock(lockObj) State = value; 
    

    public void Start()
    
        this.state = 0;
        this.mainHandler.BeginInvoke(null, null);
    


public Main()

    Monitor firstMonitor = new Monitor(20);
    firstMonitor.Start();
    do
    
        if(firstMonitor.state == 1) WriteLine("DB 20 stop working");
     while(true);

我遇到的问题是异常处理,如果MonitorHandler函数抛出异常,我没有办法知道。

我不调用 EndInvoke,所以异常不会重新抛出到主线程。

我的目标是通过简单地检查监视器实例的状态字段来检查数据库状态。 如果 throwen 中出现异常,我需要以某种方式将此异常“转移”到主线程,但我也不想开始检查状态和 Monitor 委托状态。

我很喜欢找到一种方法来访问监视器线程本身(由 .BeginInvoke 激活的线程),以便在主线程中引发异常。

谢谢。

【问题讨论】:

您在这里基本上重新实现的是轮询,不是一次而是两次,并且以最低效的方式进行。你检查过你的数据库,看看它是否已经有办法做到这一点?另见What is the X Y Problem? 如果您使用delegate.BeginInvoke,您必须调用EndInvoke,否则您的委托将永远不会被处置。如果您想从委托中获取结果(包括异常)而不进行处理,您应该使用completion callback。 罗伯特哈维这是我试图解决的问题的一个例子。我并没有真正检查数据库本身,而是检查数据库中的数据,CheckDB 函数从数据库中获取数据并检查它。如果数据超出预定义的限制(或对数据进行其他检查),则会引发警报状态。我希望主线程检查何时引发警报状态并采取相应措施。 Dour High Arch,如果我弄错了,请纠正我,但是当函数结束时,代表没有被处理?即当 state == 3 时? 不,函数结束时不会释放委托,因为它对您的类是全局的,而不是方法的局部。看看你的类,private MonitorHandlerDelegate mainHandler 是类实例,而不是本地实例。是的,如果您的代表没有外部状态或资源,那么 not disposing it won't leak resources,但这是非常危险的 you should always call EndInvoke 【参考方案1】:

我很喜欢找到一种方法来访问监视器线程本身(由 .BeginInvoke 激活的线程),以便在主线程中引发异常。

除了ThreadAbortException 之类的东西,没有将异常注入另一个任意线程的机制。

如果您打算使用委托的BeginInvoke() 方法,并且您想在与调用委托本身不同的线程中捕获异常,那么您需要在该线程中调用EndInvoke()

您的另一个选择是显式手动处理异常。 IE。在工作线程中使用try/catch 捕获异常,然后使用您自己选择的显式定义机制(例如ConcurrentQueue<T>)将捕获的异常传递给在主线程中运行的代码。

话虽如此,使用委托的BeginInvoke() 方法从来都不是真正理想的异步执行代码的方法,而今天它甚至更糟糕。从您的问题中不清楚“主线程”的性质是什么,更不用说该线程是否具有同步上下文。但假设它确实如此(例如,它是一个 GUI 线程,或一个 ASP.NET 上下文等),那么您想要的行为很容易实现,使用 Task.Run() 启动异步操作,然后在主线程中使用 await 捕获该操作的完成,以及引发的任何异常。

就此而言,即使您的主线程当前没有同步上下文,给它一个可能是正确的方法。通过利用现有机制之一,或编写自己的机制。例如,如果您希望在代码中经常遇到这种“将异常从工作线程传播到主线程”的场景,这将是一个好主意。这将允许您使用内置的语言支持来处理它(即async/await),而不必为每个实例拼凑一些东西。实现同步上下文并非易事,但您可以执行一次,然后一遍又一遍地重复使用。

【讨论】:

非常感谢您的详尽解释! .我最终确实将实现更改为 async/await 实现。

以上是关于c# 从delegate.begininvoke 捕获异常而不调用delegate.endinvoke的主要内容,如果未能解决你的问题,请参考以下文章

C# 给某个方法设定执行超时时间

C 给某个方法设定执行超时时间

如何使用 DLLImport 将字符串从 C# 传递到 C++(以及从 C++ 到 C#)?

《c# 从入门经典》 (第6版) - c# 简介

创建可以从 c# 和 c++ 中使用的 c# dll

从 C# 运行 Excel 宏:从 VBA 捕获运行时错误