воскресенье, 3 октября 2010 г.

Небольшой этюд с TPL

Что, по вашему мнению, должен вывести следующий код?

try
{
    Task.Factory.StartNew(() => Debug.WriteLine("exceptionless task"))
       .ContinueWith(t => 
            Debug.WriteLine("exception happened... " + t.Exception),
            TaskContinuationOptions.OnlyOnFaulted)
       .Wait();
}
catch (Exception exception)
{
    Debug.WriteLine("твоюж мать... " + exception);
}


 
Ну, очевидно выведет он ...


exceptionless task
твоюж мать... System.AggregateException: One or more errors occurred. ---> System.Threading.Tasks.TaskCanceledException: A task was canceled.

иначе бы я не спрашивал =) А вот почему?

Когда я пытался приспособить это к делу, то руководствовался следующей логикой. У нас есть цепочка тасков, родительский выполнился успешно, дочерний предикату не удовлетворяет и значит не будет выполнен вообще – все отлично, расходимся. Но таким способом, как показала практика, данный код не работает. Если дочерний Task предикату не удовлетворяет, то будет выброшен TaskCancellationException. Более того, это исключение будет выброшено только в том случае, если нам нужен результат выполнения цепочки тасков, то есть если в конце у нас стоит вызов Result или Wait(), а если результат не нужен, то исключение вообще никогда не выбросится, даже при финализации таска, что противоречит общей логике TPL.

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

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

Поставленную задачу вполне можно решить, например, следующим способом:

try
{
     Task.Factory.StartNew(() => Debug.WriteLine("exceptionless task"))
        .ContinueWith(t =>
        {
             if (t.IsFaulted)
             {
                    Debug.WriteLine("exception happened... " + t.Exception);
             }
        }, TaskContinuationOptions.ExecuteSynchronously)
        .Wait();
}
catch (Exception exception)
{
     Debug.WriteLine("твоюж мать... " + exception);
}

Но вот для каких практических случаев может пригодится текущее поведение TaskContinuationOptions – для меня пока загадка...

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

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