Убить заблокированную задачу в .NET 4 TPL - PullRequest
6 голосов
/ 23 апреля 2010

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

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

Ответы [ 3 ]

9 голосов
/ 06 мая 2010

Способ сделать это с помощью 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 (или блокировка).

Действительно, если вы не достаточно доверяете автору кода, чтобы он реализовал совместную отмену, тогда я бы поставил под сомнение целесообразность их запуска внутри вашего 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

0 голосов
/ 03 мая 2010

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

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

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

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

0 голосов
/ 23 апреля 2010

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

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

...