Killing a deadlocked Task in .NET 4 TPL

2019-04-08 04:27发布

问题:

I'd like to start using the Task Parallel Library, as this is the recommended framework going forward for performing asynchronous operations. One thing I haven't been able to find is any means of forcible Abort, such as what Thread.Abort provides.

My particular concern is that I schedule tasks running code that I don't wish to completely trust. In particular, I can't be sure this untrusted code won't deadlock and therefore I can't be certain if a Task I schedule using this code will ever complete. I want to stay away from true AppDomain isolation (due to the overhead and complexity of marshaling), but I also don't want to leave a Task thread hanging around, deadlocked. Is there a way to do this in TPL?

回答1:

The way to do this is with a CancellationToken and the new cancellation model. The new cancellation model is integrated into the .NET Framework in several types. The most important ones are System.Threading.Tasks, System.Threading.Tasks.Task, System.Threading.Tasks.Task and System.Linq.ParallelEnumerable.

Here's an example of your problem. This code will always deadlock because the calling code takes a lock first and then the deadlocked task tries to aquire the same lock.

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;
        }
    }
}

Task cancellation in the TPL is cooperative. In other words this will always deadlock because nothing handles the cancellation token being set to cancelled because the task thread is locked.

There is a way around this but it still relies on the authors of the untrusted code to do the right thing:

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;
    }
} 

Points to note; cancellation is cooperative. You can use Token.WaitHandle to get a handle and wait on it along with the handle(s) of other synchronization primitives. Mutex is much slower than Monitor (or lock).

Really if you don't trust the author of the code enough to have them implement cooperative cancellation then I'd question the sanity of having them run inside your AppDomain on the same thread.

For further detail see:

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



回答2:

Dan I dont think Task.Wait(timeout) will cancel this task, there is Overload Task.Wait(timeout,cancelationToken), but that only throws OperationCanceledException on task.Wait when token is signaled.

Task.Wait only blocks until either task completes or timeout expires, it does not cancel or abort task itself. So deadlocked task will stay hanging in ThreadPool. You cannot Dispose UnCompleted Task (InvalidOperation).

Im writting the same kind of application as you and i did write my own taskScheduler which allows Aborting (and is not using threadpool :( ).

But im very curious about how you solved this problem. Please respond to me.



回答3:

You simply call Task.Wait(timespanToWait).

If the task isn't complete after the specified timespan it is canceled.