Страницы

Поиск по вопросам

суббота, 8 февраля 2020 г.

Чем чревато отсутствие обработки OperationCanceledException у Task?

#c_sharp #многопоточность #tpl


Чем может быть чреват такой вот Task с необработанным исключением отмены действия,
если далее я к нему нигде не обращаюсь?

public static void Main()
{
    var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
    Task.Run(() => SomeMethod(cts.Token), cts.Token);
}

public static void SomeMethod(CancellationToken token)
{
    while (true)
    {
        token.ThrowIfCancellationRequested();
        // Некая трудоемкая операция
        Thread.Sleep(TimeSpan.FromMilliseconds(100));
    }
}


UPDATE1:
После ответа andreycha я решил проверить, как работает ловля ошибок у Task. И я заметил,
что исключение отмены не доходит до глобального обработчика и никак не вешает систему.
В кофиг файле прописал:  


  



Код:  

public static void Main()
{
    TaskScheduler.UnobservedTaskException += (object sender, UnobservedTaskExceptionEventArgs
eventArgs) =>
    {
        Console.WriteLine("Task error");
        eventArgs.SetObserved();
        (eventArgs.Exception).Handle(ex =>
        {
            Console.WriteLine("Exception type: " + ex.GetType());
            return true;
        });
    };

    CancellationTokenSource cts = new CancellationTokenSource(1000);
    Task.Factory.StartNew(() =>
    {
        Console.WriteLine("Enter");
        while (true)
        {
            cts.Token.ThrowIfCancellationRequested();
            Thread.Sleep(100);
        }
    }, cts.Token);
    Task.Factory.StartNew(() =>
    {
        throw new Exception("Some exception");
    });

    Thread.Sleep(4000);
    Console.WriteLine("Collecting");
    GC.Collect();
    GC.WaitForPendingFinalizers();

    Console.ReadLine();
}


Пример вывода:  

Task error
Exception type: System.Exception


Выходит, что если тебе никак не надо обработать отмену операции, то можно в обще
ничего не делать и ничего не будет все таки?
    


Ответы

Ответ 1



Если приложение работает под .NET Framework 4 (или под .NET Framework 4.5+ с включенной опцией ThrowUnobservedTaskExceptions), то когда сборщик мусора доберется до этого таска, финализатор выбросит исключение и приложение упадет. В .NET Framework 4.5+ поведение изменили и приложение продолжит работать (а само исключение по-прежнему можно отловить в обработчике UnobservedTaskException). Необработанное исключение ничему не помешает. Однако я бы рекомендовал всегда обзервить таски, иначе вы не узнаете, завершился ли таск успешно или нет и завершился ли вообще. В 99% случаев unobserved task -- это ошибка. Отследить завершение можно разными способами (зависит от вашей текущей архитектуры по большей степени): 1) Если вы уже используете async/await, тогда ожидайте так: var task = Task.Run(() => SomeMethod(cts.Token), cts.Token); try { await task; } catch (OperationCanceledException) { // задача была отменена } catch (Exception) { // другая ошибка } 2) Если ваш код полностью синхронный, то можно использовать либо продолжения, либо синхронное ожидание. Вариант с продолжением. Помните о том, что продолжение выполняется в том же контексте, что и оригинальный таск (т.е. в потоке из пула потоков). А значит обращаться напрямую к компонентам UI, например, нельзя. Task.Run(() => SomeMethod(cts.Token), cts.Token) .ContinueWith(SomeMethodHandler, TaskContinuationOption.OnlyOnFaulted); ... private void SomeMethodHandler(Task task) { if (task.Exception is OperationCanceledException) { // задача была отменена } else { // другая ошибка } } Вариант с синхронным ожиданием. Тут надо быть аккуратным с тем, в каком конкретно месте вы ожидаете. Поскольку внутри SomeMethod у вас бесконечный цикл, то на строке task.Wait() приложение будет висеть до тех пор, пока задача не будет отменена var task = Task.Run(() => SomeMethod(cts.Token), cts.Token); try { task.Wait(); } catch (AggregateException e) { // синхронное ожидание, в отличие от await, не "разворачивает" исключения // проверяем e.InnerExceptions на предмет наличия OperationCanceledException } P.S. Если же говорить о коде, который вы привели, то он завершит свое выполнение почти моментально, не успев произвести нужную работу. Потому что таск никто не ожидает.

Ответ 2



Если использовать библиотеку NLog - то там у класса Logger есть метод SwallowAsync, который позволяет залогировать асинхронную ошибку если она вдруг возникла. Впрочем, стандартная реализация тут в любом случае не подойдет, ведь отмена задачи - нормальный способ окончания работы, а не ошибочный. В данном случае, когда весь код свой, я бы предпочел не создавать себе проблем вместо того чтобы их героически решать: public static void SomeMethod(CancellationToken token) { while (true) { if (token.IsCancellationRequested) return; // Некая трудоемкая операция Thread.Sleep(TimeSpan.FromMilliseconds(100)); } }

Комментариев нет:

Отправить комментарий