我想写的代码是这样的:
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.
}
}
答案 0 :(得分:10)
有很多问题可以通过其他机制抛出线程,例如中止线程等,你应该找到另一种方法。
异常是一种机制,用于表示某个进程遇到了无法处理的特殊情况。您应该尽量避免编写代码,以便使用异常来表示其他经历了异常的事情。
其他线程很可能不知道如何处理异常在所有可能被代码抛出的情况下。
简而言之,您应该找到一些其他机制来中止您的线程而不是使用异常。
使用事件对象或类似物告诉线程中止其处理,这是最好的方法。
答案 1 :(得分:10)
这不是一个好主意
This article talks about ruby's timeout library.会在线程之间抛出异常。
它解释了如何从根本上打破这样的事情。它不仅仅是在ruby中被打破,而是在任何可以跨线程抛出异常的地方打破它。
简而言之,可以(并且确实)发生了什么:
ThreadA:
At some random time, throw an exception on thread B:
ThreadB:
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”标志,并使用它并节省创建异常对象的麻烦。一个挥发性的bool可以正常工作。
答案 2 :(得分:1)
在研究另一个问题时,我发现这篇文章让我想起了你的问题:
Plumbing the Depths of the ThreadAbortException using Rotor
它显示了.NET实现Thread.Abort()的旋转 - 可能任何其他跨线程异常都必须类似。 (Yeech!)
答案 3 :(得分:1)
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一起使用,未发布的句柄可能会导致内存泄漏。
以下示例说明如何使用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 :(得分:0)
我很想知道你为什么要这样做。没有一种简单的方法可以做到这一点,因为这不是一个好习惯。你可能应该回到你的设计并想出一个更清洁的方法来实现最终目标。
答案 5 :(得分:0)
我认为这不是一个好主意。 对此问题采取另一种解决方法 - 尝试使用其他机制(如共享数据)在线程之间发出信号。
答案 6 :(得分:0)
与其他人一样,我不确定这是一个好主意,但是如果你真的想这样做,那么你可以创建一个SynchronizationContext的子类,它允许发布代理并将代理发送到目标线程(如果它是一个WinForms线程工作已经完成,因为这样的子类已经存在)。目标线程必须实现某种类型的消息泵,以接收代表。
答案 7 :(得分:0)
@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
}
}
或者那只是愚蠢的?