Есть ли хороший метод в C # для выдачи исключения в данном потоке - PullRequest
11 голосов
/ 05 сентября 2008

Код, который я хочу написать, выглядит так:

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

Ответы [ 8 ]

10 голосов
/ 05 сентября 2008

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

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

Это объясняет, как выполнение такой вещи в корне нарушено. Он не просто разбит на ruby, он разбит везде, где выбрасываются исключения в потоках.

В двух словах, что может (и происходит) случиться так:

Тема A:

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», использовать его и избавить себя от необходимости создавать объект исключения. Для этого отлично подойдет летучий бул.

10 голосов
/ 05 сентября 2008

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

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

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

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

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

1 голос
/ 09 августа 2010

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

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

Использование CER ( Области ограниченного выполнения ) в C # позволяет вам высвобождать ваши ресурсы как элементарную операцию, защищая ваш код от исключений между потоками. Этот метод используется несколькими классами .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;
}
1 голос
/ 06 сентября 2008

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

Канализация глубины резьбы ThreadAbortException с помощью ротора

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

0 голосов
/ 05 сентября 2008

@ Орион Эдвардс

Я понял вашу точку зрения об исключении, которое выдается в блоке 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
        }
    }

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

0 голосов
/ 05 сентября 2008

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

0 голосов
/ 05 сентября 2008

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

0 голосов
/ 05 сентября 2008

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...