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

StackOverflow https://stackoverflow.com/questions/44656

  •  09-06-2019
  •  | 
  •  

Вопрос

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

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.
    }
}
Это было полезно?

Решение

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

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

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

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

ТредадА:

At some random time, throw an exception on thread 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();
}

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

Другие советы

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

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

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

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

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

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

Заполнение глубины ThreadAbortException с помощью Rotor

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

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

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

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

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

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

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

Я принимаю вашу точку зрения об исключении, генерируемом в блоке finally.

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

Резьба A:

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

Резьба В:

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top