[.NET]Ловить эксепшены в левых тредах

bleyman

Прога
В ней гуёвый тред и рабочий тред, который делает Всякие Вещи с последовательным портом. У проги есть кнопка "Кансел", по нажатию которой гуёвый тред а) кидает ThreadAbortException рабочему треду, б) закрывает порт. Рабочий тред вываливается из SerialPort.ReadBytes к себе на самый верх, вызывает ResetAbort и продолжает жить дальше.
Юмор ситуации состоит в том, что на самом деле где-то есть ещё и специальный тред, который в это время ждёт завершения IOCallback где-то во внутренностях порта, и который немедленно ловит эксепшен с отдиспоузнутого SafeHandle — точнее, как раз не ловит, а выкидывает, в результате чего падает всё приложение. Вот его стектрейс:
at Microsoft.Win32.Win32Native.SetEvent(SafeWaitHandle handle)
at System.Threading.EventWaitHandle.Set
at System.IO.Ports.SerialStream.AsyncFSCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* pOverlapped)
at System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* pOVERLAP)
Собссно, чо делать? Я пока только вижу вариант заменить AbortThread на посылку тому треду какой-нибудь мессаги, чтобы он ни за что не вываливался из Port.ReadBytes, но это как-то несистемно. Или сделать ещё одну обёртку вокруг порта, которая будет смиренно ждать чего-нибудь.
Вообще пиздец какой-то: http://blogs.msdn.com/bclteam/archive/2006/10/10/Top-5-Seria... (tip 4)
Эти пидарасы считают, что так всё и должно быть. Вот интересно, а как мне прервать ReadByte? Он полчаса может ждать появления байта вообще-то. Замечу, что это у них, пидарасов, эксепшен летит, а не у меня. Это они, сцуки, забыли проверить, что хэндл уже отдиспозен или сообщить той шняге, которая вызывает PerformIOCompletionCallback, что им этот коллбэк уже не нужен.
Использовать рекомендованное отключение падений по эксепшенам в рабочих тредах тоже как-то не хочется, всё-таки полезная штука.

Dasar

thread.Abort вообще не стоит делать, т.к. обычно это чревато кучей сцепэффектов
> Рабочий тред вываливается из SerialPort.ReadBytes к себе на самый верх, вызывает ResetAbort и продолжает жить дальше
раз для проги - это штатная ситуация, то лучше заменить ReadBytes или на асинхронное чтение, или на чтение с малым timeout-ом (например, нулевым)
Использовать рекомендованное отключение падений по эксепшенам в рабочих тредах тоже как-то не хочется, всё-таки полезная штука.
это как?

bleyman

> thread.Abort вообще не стоит делать, т.к. обычно это чревато кучей сцепэффектов
Это каких? Мне просто всегда казалось, что если тред чем-то занят, то ThreadAbort - вполне легитимный (и, более того, самый эффективный) способ ему сообщить, что пора прекращать. Он, конечно, должен быть правильно написан (все ресурсы обёрнуты в using и т.д.). Ну не знаю, странно как-то.
> раз для проги - это штатная ситуация, то лучше заменить ReadBytes или на асинхронное чтение, или
> на чтение с малым timeout-ом (например, нулевым)
настоящего асинхронного чтения у них как бы нет, есть возможность повеситься на эвент. Но это означает, что мне придётся заводить ещё один тред, который будет вменяемо реагировать на Thread.Abort или периодически (часто) проверять какой-нибудь флажок. Помимо всего прочего это означает, что во время чтения моя прога будет жрать 100% проца (правда, отдавать его по первому требованию). Типа, while (bla bla bla) { System.Threading.Thread.Sleep(1); }
Неее, по-моему это они пидарасы всё-таки. Как бы их в этом убедить?
> это как?
<?xml version ="1.0"?>
<configuration>
<runtime>
<legacyUnhandledExceptionPolicy enabled="1"/>
</runtime>
</configuration>

bleyman

Хорошо хоть что, как выяснилось, port.Close организует вылет из ReadBytes без спецэффектов. Вроде как.
Но это означает, что мне придётся нарушать Стройную Структуру Программы такими вот левыми хаками =(

Dasar

> настоящего асинхронного чтения у них как бы нет, есть возможность повеситься на эвент.
если у вас ничего не получается, то прочтите наконец документацию.
первый абзац документации
Use this class to control a serial port file resource. This class provides synchronous and event-driven I/O, access to pin and break states, and access to serial driver properties. Additionally, the functionality of this class can be wrapped in an internal Stream object, accessible through the BaseStream property, and passed to classes that wrap or use streams.
> Он, конечно, должен быть правильно написан (все ресурсы обёрнуты в using и т.д.). Ну не знаю, странно как-то
кто это будет гарантировать?
например, такой код правильно написан с точки зрения Thread.Abort?
Trace.WriteLine("xxxx");

Dasar

<legacyUnhandledExceptionPolicy enabled="1"/>
спасибо. очень нужная опция

kokoc88

thread.Abort вообще не стоит делать, т.к. обычно это чревато кучей сцепэффектов
Да, согласен. Например, я напарывался на такой:
The thread is not guaranteed to abort immediately, or at all. This situation can occur if a thread does an unbounded amount of computation in the finally blocks that are called as part of the abort procedure, thereby indefinitely delaying the abort.

bleyman

> если у вас ничего не получается, то прочтите наконец документацию.
Да, я уже подумал об этом и, более того, запустил рефлектор. По поводу чего объявляю типа конкурс: учаснег должен взять .net reflector, залезть в System.IO.Ports.SerialPort, найти функцию ReadByte, посмотреть, как она вызывает внутреннюю функцию SerialStream с таймаутом, после чего найти то место, где этот таймаут используется. Мне за полчаса не удалось.
При этом оно работает (то есть ReadTimeout влияет на время).
> например, такой код правильно написан с точки зрения Thread.Abort?
Да я уже в принципе понял, что да, Thread.Abort не очень хорошая штука. Как раз смотрел рефлектором на внутренности порта и прикидывал, какие нужно приложить усилия, чтобы никуда не утекли никакие ресурсы. С другой стороны, если я правильно помню, как раз во втором фреймворке появились Специальные Штуки, которые гарантируют, что во время исполнения данного куска кода не случится garbage collection и другие пакости.

Dasar

> Да, я уже подумал об этом и, более того, запустил рефлектор.
я вообще-то намекал на то, что есть например такие методы Stream.BeginRead
> после чего найти то место, где этот таймаут используется
используется при вызове SetCommTimeouts
> С другой стороны, если я правильно помню, как раз во втором фреймворке появились Специальные Штуки, которые гарантируют
я не верю, что эти штуки кто-то массово использует, а тем более правильно
в лучшем случае, они используются только уж в совсем критических местах

bleyman

> я вообще-то намекал на то, что есть например такие методы Stream.BeginRead
Да ёпт, я туда рефлектором как раз и полез, потому что попытался написать свой ReadByte через асинхронный доступ. Проблема в том, что EndRead почему-то таймаут игнорирует, а как прервать операцию вообще (ну чтобы ось поняла, что я больше не хочу читать из файла) я не нашёл.
IAsyncResult asyncRes = port.BaseStream.BeginRead(buffer, 0, 1, null, null);
asyncRes.AsyncWaitHandle.WaitOne(500, false);
int count = port.BaseStream.EndRead(asyncRes);
и вот тут она остаётся на бесконечное время.
> используется при вызове SetCommTimeouts
Ох ну слава яйцам, я уж начал думать, что с ума схожу, спасибо.
> я не верю, что эти штуки кто-то массово использует, а тем более правильно
Так идея в том, что их нужно использовать только при работе с unmanaged resources, ну и я наивно надеялся, что M$ их использовала, причём правильно =)

Dasar

> вот тут она остаётся на бесконечное время.
а почему не должна?
при отмене операции по идее правильная последовательность:
BeginRead;
Close;
try
{
EndRead
}
catch
{
}

bleyman

Как мне прервать чтение-то?
EndRead я обязан вызвать, иначе я не смогу начать читать в следующий раз — и это в лучшем случае, в худшем он сожрёт кусок данных, а то и даже попытается записать его в отдиспознутый буфер.
Как мне объяснить потоку, что я типа передумал и не хочу больше ничего читать?

bleyman

А, сорри, невнимательно посмотрел.
Так а если я не хочу закрывать поток?

Dasar

> Так а если я не хочу закрывать поток?
тогда ты хочешь что-то более сложное, и логически тебе нужна обертка (менеджер) над потоком.

bleyman

тогда ты хочешь что-то более сложное, и логически тебе нужна обертка (менеджер) над потоком.
Ыыыы. С чего бы это "более сложное"? Я хочу простой и ненавязчивой вещи: я заказал считывание байта с потока (с ожиданием а потом передумал и хочу отменить ожидание, чтобы мне вернули байт если он успел считаться, или сказали, что он не считался, и не пытались, сцуконах, продолжать считывать.
Обёртку, которая помнит, что у неё там остался запрос на считывание, можно сделать, конечно. Хм.

Dasar

> С чего бы это "более сложное"?
потому что ты хочешь сложный (транзакционный) Cancel

bleyman

о_О
Нет!
Я не хочу, чтобы несчитанные данные запихивались обратно в поток!
Я хочу, чтобы EndRead закончил считывание и вернул мне количество байт, записанных в предоставленный в BeginRead буфер. Прямо сейчас. Операционная система способна обеспечить атомарность своей же внутренней операции?
Я правильно понимаю, что это почему-то невозможно By Design?

Dasar

> Я хочу, чтобы EndRead закончил считывание и вернул мне количество байт,
т.е. ты хочешь даже не просто CancelRead, а ImmediatelyEndRead.
такой наворот должен забесплатно реализовываться в каждом из Stream-ов?
Оставить комментарий
Имя или ник:
Комментарий: