11

我想写的代码是这样的:

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.
    }
}
4

8 回答 8

10

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

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

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

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

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

于 2008-09-04T20:07:39.317 回答
10

这不是一个好主意

这篇文章讲的是 ruby​​ 的超时库。跨线程抛出异常。

它解释了如何从根本上破坏做这样的事情。它不仅在 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 可以很好地解决这个问题。

于 2008-09-04T20:55:16.097 回答
2

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;
}
于 2010-08-08T20:48:15.500 回答
1

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

使用转子探测 ThreadAbortException 的深度

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

于 2008-09-06T01:14:04.000 回答
0

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

于 2008-09-04T20:10:42.290 回答
0

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

于 2008-09-04T20:11:34.423 回答
0

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

于 2008-09-04T20:38:11.990 回答
0

@猎户座爱德华兹

关于 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
        }
    }

或者这只是愚蠢的?

于 2008-09-04T21:34:59.467 回答