![]() |
multithread & exception-safe
Доброго времени суток. Чисто теоретический вопрос: пусть есть процесс, который наплодил классов, потом разделился на несколько нитей, каждая из которых наделала своих классов. В некоторый момент в одной нити произошло исключение. Как оно правильно должно быить обработано (имеется ввиду последовательность раскрутки стека).
В однонитевом процессе всё понятно, раскручиваем до ближайшего блока try..catch либо до конца программы. А вот что делать, когда одну из нитей, вызвавшую исключение, докрутим до места её основания? Завершать все остальные нити или прекратить раскрутку? |
Что вы понимаете под раскруткой?
Добавление в блок catch оператора trow? Если так, то куда его тянуть зависит от логики программы и ни отчего более. IMHO конечно... |
Под раскруткой имеется ввиду последовательное разрушение объектов (unwind), например:
Код:
class MyForm : public Form |
Цитата:
|
pva, Вообще логично брошенное исключение хоть как-то обработать. У нас во всём, что является многонитевым "корневая" функция потока всегда завёрнута в try{}catch(...){}, который ругается в лог и просто убивает нить, заодно вычеркнув её из пула (нити никогда не завершаются, а сохраняются для следующего задания). Но у нас специфика в том, что есть пул одинаковых обработчиков, и в логику зашито, что некоторые обработчики могут падать, не выдавая, соответственно, никакого результата.
Так что мне кажется, всё зависит от логики программы. Если падает какая-то критичная для работы нить, то уж ничего не попишешь - всё придётся по возможности безболезненно убить. Хотя, в случае гуя, я думаю, библиотека должна быть по максимуму вылизана, а если исключение случается в нити или пользовательском обработчике события, то оно должно быть перехвачено (т.е. стек раскручивается до того места, из которого библиотека вызвала пользовательский код), чтобы вылез попап "произошла неизвестная ошибка, но мы тем не менее продолжаем работать". Примерно так, по-моему, поступает дельфи. А то будет обидно, если в некоем важно приложении криворукий автор не валидирует какое-нибудь текстовое поле перед его использованием и всё падает. Как-то так. |
У меня вообще вопрос пошёл из следующей ситуации (как раз описанной ivank в предыдущем посте): Есть форма, нарисованная на билдере, кнопной, с таймером и запросом. Автоматика такая:
1. Нажали кнопку, она залипла 2. через одну секнду выключилась (disable), запустился запрос 3. запрос отработал, выдал данные, включил кнопка тлипла обратно и включилась теперь в запросе происходит исключение, кнопка не возвращается из своего необычного состояния - потому что нет откатов Вот если бы это всё выполнялось "одной функцией", то есть без разбивки на OnClick и OnTimer, выглядело бы так: Код:
struct restore_pushed_t 1. Чтобы на время выполнения обработки не заблокировать процесс, вполне логично запихнуть всё это в отдельный TThread. Вроде бы теперь логично обрамлять основной цикл TThread скобками try-catch. А как вести себя, если 2 нити взаимосвязаны и сдой в обной должен разрушить другую? 2. Вариант с использованием событийной системы, чтобы не плодить нити. Разбиваем void Form1::OnClick(TObject* /*sender*/) на части: Код:
struct restore_obj |
Вот в этих статьях хорошо рассматривается работа SEH Win32, в том числе и в многопоточной среде:
1) http://www.wasm.ru/article.php?article=GordonExcept 2) http://www.wasm.ru/series.php?sid=7 Единственный нюанс: требуется мало-мальское знание Ассемблера. |
Oleg_SK, в принципе про структурные исключения хорошо расписано в SDK, который идёт с борландовскими компиляторами. Все компиляторы, которые я использовал, очень хорошо справляются с исключениями C++ в плане техники (то есть они отлавливаются). Но одной техники мало. Я приведу описанный выше пример ещё раз (скорее всего непонятно выразился):
Допустим, есть форма. На ней кнопка, таймер и какая-нибудь ерундень типа TPicture. 1. при нажатии кнопки выключаем её Button->Enabled = true и включаем таймер 2. при срабатывании таймера выключаем таймер и загружаем картинку. Потом включаем кнопку Очевидно, что исключение может возникнуть в любом месте программы (по неизвестным причинам). Например файл (картинка) не найден или заблокирован другим процессом. При возникновении исключения в событии 1 после отключения кнопки или в событии 2 до включения кнопки, кнопка остаётся отключенной навсегда. Восстановить ситуацию поможет только перезапуск приложения. А ведь исключения отработали нормально, по правилам и ситуация не такая уж небывалая... Ищу лекарство от таких болезней. |
pva, и всё же я бы советовал прочесть статьи на WASM'е(если вы этого конечно уже не сделали).
Мне терзают сомнения,что стандартные механизмы компиляторов типа try..except обладают достаточной гибкостью по сравнению c установленным вручную SEH. Вы же можете по идее в установленной/ых callback-процедуре/ах делать точную проверку состояния программы.Для этого можно использовать хоть глобальные переменные в секции данных,которые будут выступать в качестве флагов/меток. И после можно будет поправить контекст потока так,чтобы исполнение продолжилось из безопасного места. |
Здравствуйте.
Хочу заметить некоторую особенность многопоточного программироваия. Если в запускаете из потока другой поток, в котором вдруг возникает исключительная ситуация, то отловить её в первом потоке нельзя. Т.е. например для C# если блок try - catch содержит функцию my_thread.Start(), а функция потока my_thread генерирует исключение, то упомянутый блок try - catch его не перехватывает. Для перехвата данного исключения необходимо предусмотреть свой try - catch в обработчике потока my_thread. Если новый поток создаётся по событию из пула потоков, то данные рассуждения применимы и к нему. Удачи. |
podsyp, а статьи почитать при этом лень?
SEH -- это потокозависимый механизм, т.е. свой для каждого потока.Поэтому если его не установить в потоке,то исключение,которое произойдёт в этом потоке,не будет поймано. |
DillerInc, Во-первых SEH и плюсовые (или шарповые) исключения это всё-таки разные вещи (хотя в VC++ плюсовые исключения, вроде, сделаны поверх SEH. По-крайней мере, раньше были). Во-вторых, podsyp именно о потокозависимости и сказал.
|
Просто он это сказал так,словно бы Америку открыл.В то время как этому есть вполне нормальное объяснение.
|
а что происходит, если во втором потоке выплыло исключение, но неперехватилось? убивается только поток или вся программа?
|
Здравствуйте.
Высылаю код на С#, связанный с вопросом. Короче говоря - ваше исключение не перехватывается. Программа нормально не работает. А имеет она 1 или несколько потоков выполнения по моему не имеет значения. Удачи. using System; using System.Collections.Generic; using System.Text; namespace ExceptionTester { class Program { static void Main(string[] args) { try { Runner r = new Runner(); r.Start();//здесь запускается новый поток } catch (Exception ex) { Console.WriteLine(ex.ToString()); } } } class Runner { private System.Threading.Thread t; public Runner() { t = new System.Threading.Thread(new System.Threading.ThreadStart(thr_func)); } public void Start() { t.Start(); } private void thr_func() { Console.WriteLine("I am started!"); while (true) { System.Threading.Thread.Sleep(100); Console.Read(); throw new Exception("never catched"); } } } } |
Цитата:
podsyp, вы статьи читали?Вы думаете,что этот "шарп" за вас всё сделает,а вам останется только нажать на кнопочку компиляция...?? |
Мне кажется, что вы ищете проблему там, где её нет. Ну не нравится вам шарп, ну создайте вы аналогичную программу на С++. Да, слово SEH красивое. Но это не повод изучать компилятор, а тем более переделывать его. Я больше чем уверен, что в С++ что то похожее можно изобразить, но придётся иметь дело с WinAPI (CreateThread, Callback и т.д.) и код на десять строчек не получится как в шарпе. А суть в ворохе программного кода теряется. Поэтому и возникают вопросы: "А что будет если я буду перехватывать исключение, созданное в одном потоке, в другом потоке?" Многопоточность задумывалась как возможность одновременного независимого выполнения разных задач. Это примерно так, что у вас работает Word и Excel, Word грохается, а увидеть мы это должны в Excel. Знаю, знаю я перепутал процессы с потоками и всё такое. Но по моему, то как работает компилятор шарпа - это правильно. Да есть хороший приём, когда мы не видим throw, а есть блок try - catch. Он генерируется в методах классов, которые мы используем. Можем даже отнаследовавшись генерировать свои исключения. Видимо всё это и рождает подобные вопросы. Вроде thread.Start() и есть вызов метода. Это заблуждение. Извините за прямоту. Удачи.
|
Цитата:
Цитата:
Цитата:
Короче,вам решать.Будете ли вы дальше продолжать "спать" под нежный шелест вашего ЯВУ,либо попытаетесь разобраться во всем,как следует.Во втором случае необходимо первым делом в принудительном порядке читать Гордона: http://www.wasm.ru/article.php?article=GordonExcept Ну,а дальше можно и Мэта Питрека. |
Здравствуйте.
Я не расчитываю встретить понимание, но всё же задумайтесь. Не заостряясь на в статье по поводу SEH решалась задача как из одного потока узнать что исключение произошло в другом. Вот как это делается в шарпе на примере исключения деления на ноль: using System; namespace ConsoleApplication4 { public delegate void EventDeledate(object o, ExcEventArgs e); public class ExcEventArgs : EventArgs { public string Message; public ExcEventArgs(string message) { Message = message; } } class Program { private static string _exc_message; static void Main(string[] args) { _exc_message = ""; Console.WriteLine("main thread started"); Runner r = new Runner(); r.OnException += new EventDeledate(r_OnException); r.Run();//запуск другого потока System.Threading.Thread.Sleep(1000);//ожидание события в другом потоке Console.WriteLine(_exc_message);//вывод информации о событии, произошедшей в другом потоке Console.Read(); } static void r_OnException(object o, ExcEventArgs e) { _exc_message = e.Message; } } class Runner { private System.Threading.Thread _thread; public event EventDeledate OnException; public Runner() { _thread = new System.Threading.Thread(new System.Threading.ThreadStart(Thread_Func)); } public void Run() { _thread.Start(); } private void Thread_Func() { try { Console.WriteLine("Another thread started..."); int divisor = 0; int res = 1 / divisor;//генерация исключения } catch (DivideByZeroException ex) { OnException(this, new ExcEventArgs("division by zero!"));//генерация события } } } } Не надо менять службы операционной системы. Не надо менять компилятор. Надо только воспользоватся событиями. Майкрософт пишет очень хороший софт, но очень плохую литературу, объясняющую как этот софт устроен. Поверьте, ни одна фирма не будет разглашать суть и способы создания программного обеспечения. А задурить башку - всегда пожалуйста. |
podsyp, эээ...вы не путайте,пожалуйста,вкусное со сладким.То,что вы пытаетесь тут растолковать,попадает уже под разряд синхронизации,а вовсе не внутрипоточной обработки исключений.
Да,такое извращение,как вы тут описали,может иметь место.Но представьте это на практике: вы запускаете два потока,один из которых ничего не делает,а только ждёт события,которое оповестит о произошедшей в другом потоке ошибке.Получается,что по логике возникновение ошибки в вашем коде является почти запланированным, т.е. вы специально пишете кривой код полный багов на славу "гениальных" творений мелкомягких?! Хорошо,запланированные исключения иногда могут дать большие возможности,например,программа,которая трассирует сама себя с помощью исключения EXCEPTION_SINGLE_STEP.Но наверно ни одному трезвомыслящему программисту не придёт в голову использовать для этого объекты синхронизации именно в таком виде,как вы описали. Если вы хотите отлаживать программу,то не изобретайте велосипед,используйте DebugAPI.Там вы и будете ловить все исключения,как отладочные события. Цитата:
Цитата:
|
Здравствуйте DillerInc. Похоже форум превращается в диалог. Вы пишете, что в коде, который я представил, поток являющийся точкой входа в программу, ничего не делает, а только ждёт запланированного исключения. Не согласен с этим. Вместо Sleep можно поставить другие операторы и вообще опрашивать состояние переменной _exc_message в цикле попутно выполняя полезную работу. Правда у имеющейся программы есть недостаток: о создавшемся исключении основной поток узнаёт только через секунду. Кстати DillerInc, напишите что нужно сделать, чтобы вызвавший поток мгновенно оповещался о наступлении исключения в другом потоке. В статье Гордона это упоминалось. О недостатках такого способа вы как раз написали. Удачи.
|
podsyp, касательно многопоточных приложений в статье Гордона упор делается на корректное завершение потоков,которые непричастны к возникновению ошибки.Речь идёт о завершении процесса.
* Для этого первым делом в главном потоке устанавливается свой финальный обработчик с помощью функции SetUnhandledExceptionFilter. * Когда система будет намерена завершить процесс из-за необработанного исключения,она вызовет этот наш финальный обработчик. * Далее используется глобальная переменная,которая в финальном обработчике выставляется,например,в значение TRUE.Потоки приложения должны в цикле полезной работы проверять значение этой переменной.Если значение переменной равно TRUE, то они должны освободить ресурсы и завершится. * Завершение потока есть событие.Поэтому в финальном обработчике после установки переменной необходимо вызвать функцию WaitForMultipleObjects, чтобы ожидать завершения всех указанных потоков. * Затем выход из финального обработчика,и процесс убивается системой. Вот так вот. |
Я ждал от вас DillerInc этого ответа. Но по моему прелесть многопоточности в том, что в одном потоке вызывается и перехватывается исключение , а другие работают себе как ни в чём ни бывало. А так представьте - один поток у вас музыку играет, другой анимацию делает, а третий вычисления производит. И вдруг в ходе вычислений происходит деление на ноль. Так что - все остальные потоки должны при этом завершится, пусть даже с корректным освобождением ресурсов? Так было в старые времена, когда никто про Windows и слыхом не слыхивал, работает себе программа и случается ошибка. И всё встаёт. В вашем описанном случае тоже не всё здорово? Возможно это и необходимо для программ, в которых не предусмотренно использования try - catch в каждом потоке. Но вообще как мне кажется включать для каждого потока свой try - catch хорошая практика. Пока она не "узаконена" производителями компиляторов, но ведь например раньше и такого понятия как свойство не было. Зато призывов не обращатся к внутренним переменным класса напрямую - в любой книжке по программированию было навалом. Функция WaitForMultipleObjects судя по названию как раз и занимается тем, что сидит и ждёт пока что нибудь страшное не случится. Видимо её я как раз и имел в виду когда спрашивал о мгновенном оповещении. Только в шарпе у неё оболочка AutoResetEvent. Ожидание производится функцией WaitOne(), а событие порождается функцией Set(). Создаёте отдельный поток для ожидания события который всего и делает что асинхронно ждёт наступления события. А вы меня ещё укоряли как это так моя программа ожидает запланированного исключения. Альтернативный вариант - это использование событий, как в том варианте что я выкладывал. Преимущество событий перед AutoResetEvent в том что они помимо сигнализации о себе могут передать информацию. И при этом не нужно создавать глобальных переменных. Для маленьких программ создание глобальных переменных может быть не такое уж зло, но для больших - крайне не желательно. Программа становится как бы завязанной в один узел, её трудно разделить на независимые части, поскольку для всех переменные одни. Удачи.
|
В общем,мне надоело уже тут биться об стену.Если вы такой умный,то разбирайтесь сами со своими "до-диезами".
|
DillerInc, 2 поста назад - свежий взгляд (для моих мозгов), это тема, чёто не подумал раньше как-то. Ведь действиельно, если поток ещё работоспособный, он должен закончиться естественным образом (то бишь по проверке isApplicationTerminated).
podsyp, я бы всё-таки почитал литературу от создателей С++ (только не через призму С#), всё задумывалось совсем не так, а потом ребята из sun всё извратили, хотя получилось неплохо. Когда программа завершается (любым способом), операционка может почистить за ней мусор и перезапустить её при необходимости, так что ничего не встанет. Слишком самоуверенные программы (которые в случае необработанного искобчения продолжают работать) несут большой риск... |
Время: 09:34. |
Время: 09:34.
© OSzone.net 2001-