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