C# 中是不是有在给定线程上引发异常的好方法

Posted

技术标签:

【中文标题】C# 中是不是有在给定线程上引发异常的好方法【英文标题】:Is there a good method in C# for throwing an exception on a given threadC# 中是否有在给定线程上引发异常的好方法 【发布时间】:2010-09-07 20:42:33 【问题描述】:

我想写的代码是这样的:

void MethodOnThreadA()

    for (;;)
    
        // Do stuff
        if (ErrorConditionMet)
            ThrowOnThread(threadB, new MyException(...));
    


void MethodOnThreadB()

    try
    
        for (;;)
        
            // Do stuff
        
    
    catch (MyException ex)
    
        // Do the right thing for this exception.
    

我知道我可以让线程 B 以线程安全的方式定期检查线程 A 是否设置了标志,但这会使代码更加复杂。有没有更好的机制可以使用?

这是一个更详细的定期检查示例:

Dictionary<Thread, Exception> exceptionDictionary = new Dictionary<Thread, Exception>();

void ThrowOnThread(Thread thread, Exception ex)

    // the exception passed in is going to be handed off to another thread,
    // so it needs to be thread safe.
    lock (exceptionDictionary)
    
        exceptionDictionary[thread] = ex;
    


void ExceptionCheck()

    lock (exceptionDictionary)
    
        Exception ex;
        if (exceptionDictionary.TryGetValue(Thread.CurrentThread, out ex))
            throw ex;
    


void MethodOnThreadA()

    for (;;)
    
        // Do stuff
        if (ErrorConditionMet)
            ThrowOnThread(threadB, new MyException(...));
    


void MethodOnThreadB()

    try
    
        for (;;)
        
            // Do stuff
            ExceptionCheck();
        
    
    catch (MyException ex)
    
        // Do the right thing for this exception.
    

【问题讨论】:

哦哇哦,如果可以的话,你应该期望即使是简单的添加也可以看到抛出异常。 【参考方案1】:

这不是个好主意

This article talks about ruby's timeout library. 跨线程抛出异常。

它解释了如何从根本上破坏做这样的事情。它不仅在 ruby​​ 中被破坏,它在任何跨线程引发异常的地方都被破坏。

简而言之,可以(并且确实)发生的事情是这样的:

线程A:

At some random time, throw an exception on thread B:

线程B:

try 
    //do stuff
 finally 
    CloseResourceOne();
    // ThreadA's exception gets thrown NOW, in the middle 
    // of our finally block and resource two NEVER gets closed.
    // Obviously this is BAD, and the only way to stop is to NOT throw
    // exceptions across threads
    CloseResourceTwo();

您的“定期检查”示例很好,因为您实际上并没有跨线程抛出异常。 您只是设置了一个标志,上面写着“下次查看此标志时抛出异常”,这很好,因为它不会受到“可以在你的捕获或最终阻塞的中间抛出”问题的影响。 但是,如果您要这样做,您不妨设置一个“exitnow”标志,并使用它并省去创建异常对象的麻烦。一个 volatile bool 就可以了。

【讨论】:

【参考方案2】:

有足够多的异常问题可以通过其他机制在线程上抛出,例如中止线程等,您应该找到另一种方法。

异常是一种机制,用于表明进程遇到了无法处理的异常情况。您应该尽量避免编写代码,以便使用异常来表示其他事情遇到了异常情况。

其他线程很可能不知道如何处理异常在您的代码可能引发的所有情况下

简而言之,您应该找到一些其他机制来中止线程,而不是使用异常。

使用事件对象或类似的东西来告诉线程中止它的处理,这是最好的方法。

【讨论】:

【参考方案3】:

Orion Edwards 所说的并不完全正确:不是“唯一”的方式。

// Obviously this is BAD, and the only way to stop is to NOT throw
// exceptions across threads

在 C# 中使用 CER(受约束的执行区域)允许您将资源作为原子操作释放,从而保护您的代码免受线程间异常的影响。 .NET Framework 的几个类都使用了这种技术,这些类与 Windows 的本机 API 一起工作,其中未释放的句柄可能会导致内存泄漏。

见http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.runtimehelpers.prepareconstrainedregions.aspx

以下示例展示了如何使用PrepareConstrainedRegions 方法可靠地设置句柄。要可靠地将句柄设置为指定的预先存在的句柄,您必须确保在 SafeHandle 对象中分配本机句柄和随后对该句柄的记录是原子的。这些操作之间的任何故障(例如线程中止或内存不足异常)都将导致本机句柄泄漏。可以使用PrepareConstrainedRegions方法确保句柄不泄露。

就这么简单:

public MySafeHandle AllocateHandle()

    // Allocate SafeHandle first to avoid failure later.
    MySafeHandle sh = new MySafeHandle();

    RuntimeHelpers.PrepareConstrainedRegions();
    try  
    finally  // this finally block is atomic an uninterruptible by inter-thread exceptions
    
        MyStruct myStruct = new MyStruct();
        NativeAllocateHandle(ref myStruct);
        sh.SetHandle(myStruct.m_outputHandle);
    

    return sh;

【讨论】:

【参考方案4】:

在研究另一个问题时,我看到这篇文章让我想起了你的问题:

Plumbing the Depths of the ThreadAbortException using Rotor

它显示了 .NET 为实现 Thread.Abort() 所经历的变化——大概任何其他跨线程异常都必须类似。 (耶!)

【讨论】:

【参考方案5】:

我很想知道您为什么要这样做。没有一种简单的方法可以做到这一点,因为这不是一个好习惯。您可能应该回到您的设计并找出一种更简洁的方法来实现最终目标。

【讨论】:

【参考方案6】:

我不认为这是一个好主意.. 再解决这个问题 - 尝试使用其他机制,如共享数据在线程之间发出信号。

【讨论】:

【参考方案7】:

像其他人一样,我不确定这是一个好主意,但如果你真的想这样做,那么你可以创建一个 SynchronizationContext 的子类,它允许将委托发布和发送到目标线程(如果它是一个 WinForms线程为您完成了工作,因为这样的子类已经存在)。目标线程必须实现某种等效的消息泵,才能接收委托。

【讨论】:

【参考方案8】:

@Orion Edwards

我同意你关于 finally 块中抛出异常的观点。

但是,我认为有一种方法 - 使用另一个线程 - 使用这种异常作为中断的想法。

线程 A:

At some random time, throw an exception on thread C:

线程 B:

try 
    Signal thread C that exceptions may be thrown
    //do stuff, without needing to check exit conditions
    Signal thread C that exceptions may no longer be thrown

catch 
    // exception/interrupt occurred handle...

finally 
    // ...and clean up
    CloseResourceOne();
    CloseResourceTwo();

线程 C:

 while(thread-B-wants-exceptions) 
        try 
            Thread.Sleep(1) 
        
        catch 
            // exception was thrown...
            if Thread B still wants to handle exceptions
                throw-in-B
        
    

或者这只是愚蠢的?

【讨论】:

以上是关于C# 中是不是有在给定线程上引发异常的好方法的主要内容,如果未能解决你的问题,请参考以下文章

测试装饰器引发的异常的好方法是啥?

您可以在不同的线程上重新引发 .NET 异常吗?

如何在OnExceptionAsync c#中获取引发异常的方法名称[重复]

在 C# 中出现异常时重新启动线程 [重复]

C# 捕获堆栈溢出异常

使用异步方法等待 Task.Run 不会在正确的线程上引发异常