Есть ли хороший метод в С# для исключения исключения в заданном потоке

Код, который я хочу написать, выглядит следующим образом:

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

Это НЕ хорошая идея.

В этой статье рассказывается о Ruby timeout library., которая генерирует исключения из потоков.

В нем объясняется, как такое дело нарушается. Он не просто сломан в рубине, он сломан в любом месте, который генерирует исключения по потокам.

Вкратце, что может (и происходит), это:

ThreadA:

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

ThreadB:

try {
    //do stuff
} finally {
    CloseResourceOne();
    // ThreadA 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();
}

Пример вашей периодической проверки в порядке, так как вы фактически не бросаете исключения из потоков.
Вы просто устанавливаете флаг, в котором говорится: "Бросьте исключение в следующий раз, когда вы посмотрите на этот флаг", что прекрасно, поскольку он не страдает от проблемы "может быть брошен в середине вашей проблемы с catch или finally block".
Однако, если вы собираетесь это сделать, вы можете просто установить флаг "exitnow" и использовать это и избавить себя от необходимости создавать объект исключения. Волатильный bool будет отлично работать для этого.

Ответ 2

Есть достаточно проблем с исключениями, которые могут быть переданы на потоки другими механизмами, такими как прерывание потоков и т.п., что вы должны найти другой способ сделать это.

Исключение - это механизм, используемый для обозначения того, что процесс имеет нечто исключительное, с чем он не может справиться. Вы должны стараться избегать написания кода, чтобы исключение использовалось, чтобы сигнализировать, что что-то еще пережило нечто исключительное.

Этот другой поток, скорее всего, не знает, как обрабатывать исключение во всех случаях, когда он может быть брошен вашим кодом.

Короче говоря, вы должны найти другой механизм для прерывания ваших потоков, чем использование исключений.

Используйте объекты событий или похожие, чтобы сообщить потоку, чтобы прервать его обработку, что наилучшим образом.

Ответ 3

Во время исследования другого вопроса я наткнулся на эту статью, которая напомнила мне о вашем вопросе:

Включение глубин исключения ThreadAbortException с помощью ротора

Он показывает, какие переменные .NET реализует для реализации Thread.Abort() - предположительно любое другое исключение перекрестных потоков должно быть схожим. (Yeech!)

Ответ 4

То, что говорит Орион Эдвардс, не совсем верно: это не единственный способ.

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

Использование CER (Ограниченные области выполнения) в С# позволяет вам освобождать ресурсы как атомную операцию, защищая ваш код от межпоточных исключений. Этот метод используется несколькими классами .NET Framework, которые работают с собственным API-интерфейсом Windows, где неизданный дескриптор может вызвать утечку памяти.

См. 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;
}

Ответ 5

Мне интересно узнать, почему вы хотели бы это сделать. Там нет простого способа сделать это, потому что это не очень хорошая практика. Вы, вероятно, должны вернуться к своему дизайну и выяснить более чистый способ достижения конечной цели.

Ответ 6

Я не думаю, что это хорошая идея.. Возьмите еще одну трещину в этой проблеме. Попробуйте использовать какой-либо другой механизм, такой как общие данные, для передачи сигналов между потоками.

Ответ 7

Как и другие, я не уверен, что такая хорошая идея, но если вы действительно этого хотите, тогда вы можете создать подкласс SynchronizationContext, который позволяет отправлять и отправлять делегаты в целевой поток (если это WinForms нить, работа выполняется для вас, поскольку такой подкласс уже существует). Целевой поток должен будет реализовать какой-то эквивалент насоса сообщения, хотя для приема делегатов.

Ответ 8

@Orion Edwards

Я хочу сказать, что исключение выбрасывается в блок finally.

Однако, я думаю, есть способ - использовать еще один поток - использовать эту идею исключения-прерывания.

Тема А:

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

Или это просто глупо?