Устранение зависшей задачи в .NET 4 TPL

Я хочу начать использовать параллельную библиотеку задач, так как это рекомендованная среда для выполнения асинхронных операций. Единственное, что мне не удалось найти, - это какие-либо средства принудительного прерывания, такие как то, что предоставляет Thread.Abort.

Меня особенно беспокоит то, что я планирую задачи, выполняющие код, которому я не хочу полностью доверять. В частности, я не могу быть уверен, что этот ненадежный код не заблокируется, и поэтому я не могу быть уверен, будет ли когда-либо завершена задача, которую я планирую с использованием этого кода. Я хочу держаться подальше от настоящей изоляции AppDomain (из-за накладных расходов и сложности маршалинга), но я также не хочу, чтобы поток задач зависал в тупике. Есть ли способ сделать это в TPL?


person Dan Bryant    schedule 23.04.2010    source источник


Ответы (3)


Это можно сделать с помощью CancellationToken и новой модели отмены. Новая модель отмены интегрирована в .NET Framework в нескольких типах. Наиболее важными из них являются System.Threading.Tasks, System.Threading.Tasks.Task, System.Threading.Tasks.Task и System.Linq.ParallelEnumerable.

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

public void Example()
{
    object sync = new Object();
    lock (sync)
    {
        CancellationTokenSource canceller = new CancellationTokenSource();
    ManualResetEvent started = new ManualResetEvent(false);
        Task deadlocked = Task.Factory.StartNew(() => 
            { 
            started.Set();
                // EVIL CODE: This will ALWAYS deadlock
                lock(sync) { }; 
            }, 
            canceller.Token);

        // Make sure task has started.
    started.WaitOne(); 

        canceller.Cancel();

        try
        {
            // Wait for task to cancel.
            deadlocked.Wait();
        }
        catch (AggregateException ex) 
        {
            // Ignore canceled exception. SIMPLIFIED!
            if (!(ex.InnerException is TaskCanceledException))
                throw;
        }
    }
}

Отмена задачи в TPL - кооперативная. Другими словами, это всегда будет взаимоблокировкой, потому что ничто не обрабатывает токен отмены, установленный на отмену, потому что поток задачи заблокирован.

Есть способ обойти это, но он по-прежнему полагается на то, что авторы ненадежного кода поступят правильно:

public static void Example2()
{
    Mutex sync = new Mutex(true);

    CancellationTokenSource canceller = new CancellationTokenSource();
    bool started = false;

    Task deadlocked = Task.Factory.StartNew(() =>
        {
            started = true;
            // EVIL CODE: This will ALWAYS deadlock 
            WaitHandle.WaitAny(new WaitHandle[] { canceller.Token.WaitHandle, sync });
        },
        canceller.Token);

    // Make sure task has started.
    while (!started) { }

    canceller.Cancel();

    try
    {
        // Wait for task to cancel. 
        deadlocked.Wait();
    }
    catch (AggregateException ex)
    {
        // Ignore canceled exception. SIMPLIFIED! 
        if (!(ex.InnerException is TaskCanceledException))
            throw;
    }
} 

На заметку; отмена возможна. Вы можете использовать Token.WaitHandle, чтобы получить дескриптор и ожидать его вместе с дескриптором (ами) других примитивов синхронизации. Mutex намного медленнее, чем Monitor (или lock).

На самом деле, если вы недостаточно доверяете автору кода, чтобы они реализовали совместную отмену, я бы поставил под сомнение разумность их запуска внутри вашего AppDomain в одном потоке.

Для получения дополнительной информации см .:

http://msdn.microsoft.com/en-us/library/dd997364.aspx

http://msdn.microsoft.com/en-us/library/dd537607.aspx

http://msdn.microsoft.com/en-us/library/ee191552.aspx

person Ade Miller    schedule 06.05.2010
comment
Спасибо, это полезная информация. Однако у меня сложилось впечатление, что задача Задачи прослушать запрос на отмену и самостоятельно выбросить OperationCancelledException, если он не может быть полностью завершен. Я опробую ваш код, когда у меня будет возможность сегодня днем. - person Dan Bryant; 06.05.2010
comment
По первой ссылке слушатели могут быть уведомлены о запросах отмены путем опроса, регистрации обратного вызова или ожидания дескрипторов ожидания. Таким образом, Task.Wait вызывает прослушивание. - person Ade Miller; 06.05.2010
comment
См .: stackoverflow.com/questions/2293976/ для примера использования IsCancellationRequested для проверки отмены и ответа на нее. - person Ade Miller; 06.05.2010
comment
Код работает не так, как описано. У меня все еще возникают тупиковые ситуации (примерно в половине случаев). - person totorocat; 14.07.2010
comment
Дэн, тоторокат, извиняюсь! Я написал этот исходный код, путешествуя без необходимых исследовательских материалов. Я исправил код и добавил больше деталей в образец, показывающий подход к совместной отмене, что, как вы упомянули, является правильным способом подумать об этом. - person Ade Miller; 01.08.2010
comment
Хотя он работает, он не справляется с ситуацией, когда что-то идет не так, и вам нужно Abort. Если вы используете сторонний код, вы не можете быть уверены, что они поступят правильно. Я бы хотел, чтобы MS обратилась к этому. - person Kelly; 28.10.2010

Дэн: Я не думаю, что Task.Wait (тайм-аут) отменит эту задачу, есть Overload Task.Wait (timeout, cancellationToken), но это только вызывает OperationCanceledException в task.Wait, когда появляется сигнал токена.

Task.Wait блокируется только до завершения задачи или истечения тайм-аута, она не отменяет и не прерывает саму задачу. Таким образом, тупиковая задача будет зависать в ThreadPool. Вы не можете удалить незавершенную задачу (InvalidOperation).

Я пишу такое же приложение, как мы с вами, написал свой собственный taskScheduler, который позволяет прерывание (и не использует threadpool :().

Но мне очень любопытно, как вы решили эту проблему. Пожалуйста, ответьте мне.

person bosko    schedule 03.05.2010
comment
После дальнейшего рассмотрения я решил, что если может произойти тупик, Thread.Abort на самом деле только маскирует любую основную проблему, так как состояние уже могло быть повреждено. Таким образом, я считаю, что отказ от завершения работы является фатальным (оператор может продолжить ожидание или завершить приложение). Для моего приложения этого достаточно, поскольку это не критично; если бы приложение было критически важным, мне пришлось бы перейти на использование доменов приложений или отдельный процесс хостинга для частично доверенного кода. - person Dan Bryant; 03.05.2010
comment
Дэн, я думаю, это правильный способ думать об этом. Thread.Abort определенно не решение. Это может оставить ваше приложение в неизвестном состоянии. - person Ade Miller; 05.08.2010

Вы просто звоните Task.Wait(timespanToWait).

Если задача не завершена по истечении указанного промежутка времени, она отменяется.

person Foxfire    schedule 23.04.2010
comment
Спасибо. Это было совсем не очевидно из документации, но это имеет смысл, если вы думаете о задачах как о изолирующем вас от любых деталей того, как Задачи планируются и управляются. Вы знаете, нужно ли мне сделать что-нибудь особенное после того, как Wait вернется, прежде чем я смогу безопасно избавиться от задачи? - person Dan Bryant; 23.04.2010
comment
Это не ответ. Wait () сделает именно это, подождите. Это не отменит / прервет задачу. - person Ade Miller; 04.05.2010