Сегодня расскажу о замечательных индусах из Microsoft, которые вначале делают, а потом думают.
Конкретно речь пойдёт про Таски. Не буду про них расписывать, ибо не это тема сегодняшнего поста. Вкратце, это такие треды на стероидах с вкусными и удобными фичам.
Одна из фич, это проброс эксепшенов наружу. Примерно так:
Конкретно речь пойдёт про Таски. Не буду про них расписывать, ибо не это тема сегодняшнего поста. Вкратце, это такие треды на стероидах с вкусными и удобными фичам.
Одна из фич, это проброс эксепшенов наружу. Примерно так:
var t = new Task(() => { throw new Exception(); }); t.Start(); // do something try { t.Wait(); } catch (AggregateException) { Console.WriteLine("Exception occurred"); throw; }
Т.е. мы что-то выполняем в отдельном потоке, в это время занимаемся своими вещами, а потом убеждаемся, что таск закончился и ловим исключение, которое за нас обернули в AggregateException (настоящее скрыто внутри его).
Т.е. получается вполне прикольная фича, можно честно ловить исключения из других потоков в то время, когда мы знаем, как их обработать. И этим можно активно пользоваться (например, скрывая всю параллельную работу внутри метода, но пробрасывая исключения наружу).
Но есть маленький нюанс. Эксепшен ведь пробрасывается, когда от таска что-то ждут (результат, или просто завершение). В стандартной терминологии это называется observable. Т.е. таск должен быть наблюдаемым. А что если мы запустили и забыли (или забили)?
new Task(() => { throw new Exception(); }).Start();
Получается, что эксепшен куда-то потеряется? Как бы не так! Доблестные спецы из Microsoft подумали, и придумали бросать исключение в деструкторе. Оцените изящество этого решения: нарушает все вменяемые стандарты кодирования, приводит к падению приложения, и выдаёт ошибки в то время и в том месте, где их отловить невозможно вообще. Супер!
Наверное, они решили, что раз исключение в обычном треде вызывает падение приложения, то и тут надо. Только забыли про это маленькое отличие с исключениями: если мы хотим сделать безопасным обычный тред, нам достаточно написать try/catch и все станут счастливы (всё равно никто за нас ошибки не обработает). А случае с тасками, кидать исключение изнутри наружу можно и местами иногда полезно. Но получается, что за этими тасками приходится следить как за маленькими детьми, хотя нам вполне может быть глубоко наплевать в данном месте на результат конкретной операции.
Решений данной проблемы в принципе можно придумать несколько (различные врапперы, следить за использованием тасков), одно из достаточно красивых такое:
public static Task IgnoreErrors(this Task t) { t.ContinueWith(x => { var e = x.Exception; }, TaskContinuationOptions.OnlyOnFaulted); return t; } private static void DoTask() { new Task(() => { throw new Exception(); }).IgnoreErrors().Start(); }
Т.е. мы создаём extension-метод, который прикрепляет к нашей таске продолжение (continuation), в котором мы берём значение свойства Exception (это обязательно надо сделать! именно так таска становится опять наблюдаемой (observable)), и на этом успокаиваем систему.
Задача решена, и надеюсь, что у вас она вызовет мата меньше чем у меня в своё время, когда я попался на такую проблему. А попался я на неё ещё по одной причине.
Бонус
Я вначале не зря писал, что в Microsoft вначале сделали, а потом начали думать. А подумав, они поняли, что поведение уж больно идиотское и убивать всё приложение никому не нужно. И в 4.5 изменили логику, так что для 4.5 уже мой пост неактуален.Но без нюансов у Microsoft не обходится, ибо 4.5 по факту подменяет собой 4.0. Сюрприз! Т.е. установив 4.5, все приложения, написанные под 4.0 начинают по факту работать на 4.5, и слегка по-другому. Например, в данном случае, ошибка уже не появится (попадаются и некоторые другие различия в поведении, но это уж слишком выраженное). Вернуть старое поведение можно конфигом, но кто же заморачивается чтением всяких безумных и бесполезных настроек?
Т.е. вы как разработчик поставили все апдейты, написали приложение, протестировали в хвост и в гриву, а падает оно только у клиента. Ибо у клиента стоит Windows Server 2003, и ваше приложение должно с ним работать, и даже работает, только падает. Иногда. С непонятным стэктрейсом. И вы пытаетесь найти хвосты и поправить всё это безобразие.
Иногда просто хочется убиться головой об стену от таких гениальных поворотов сюжета.
Комментариев нет:
Отправить комментарий