views:

466

answers:

3

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?

A: 

You simply call Task.Wait(timespanToWait).

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

Foxfire
Thanks. This was not at all obvious from the documentation, but it makes sense when you think of Tasks as insulating you from any details of how Tasks are scheduled and managed. Do you know if I'll have to do anything special after Wait returns before I can safely Dispose the task?
Dan Bryant
This isn't the answer. Wait() will do just that, wait. It will not cancel/abort the task.
Ade Miller
A: 

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.

bosko
Upon further consideration, I decided that if a deadlock could occur, Thread.Abort is really only masking any underlying problem, as state may already have been corrupted. As such, I'm considering a failure to terminate to be fatal (operator has option to keep waiting or terminate application). This is sufficient for my app as it is not mission critical; if the app were mission critical, I would have to migrate to using AppDomains or a separate hosting process for the partially trusted code.
Dan Bryant
Dan, I think this is the correct way to think about this. Thread.Abort is definitely not a solution. It may leave your application in an unknown state.
Ade Miller
+3  A: 

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

Ade Miller
Thanks, this is useful information. However, I was under the impression that it's up to the Task to listen for the Cancellation request and throw OperationCancelledException on its own if it could not fully complete. I'll try out your code when I get a chance this afternoon.
Dan Bryant
From the first link, "Listeners can be notified of cancellation requests by polling, callback registration, or waiting on wait handles." So effectively Task.Wait causes the listen to occur.
Ade Miller
See: http://stackoverflow.com/questions/2293976/how-and-if-to-write-a-single-consumer-queue-using-the-task-parallel-library/2779208#2779208 for an example of using IsCancellationRequested to check for cancellation and respond to it.
Ade Miller
The code doesn't appear to work as described. I'm still getting deadlocks (about half the time).
totorocat
Dan, totorocat, Apologies! I wrote this original code while travelling without the right research materials to hand. I've corrected the code and added more detail to the sample showing an approach to cooperative cancellation, which as you mention is the right way to think about this.
Ade Miller
While it works it doesn't handle the case where something goes horribly wrong and you need an Abort. If your using 3rd party code you cannot be assured they will do the right thing. I wish MS would address this.
Kelly