Saturday, 13 July 2019

Threads Mashup

After writing this post I was thinking a bit about how we could do to cancel a chain of async-await calls in JavaScript. Standard promises are not cancellable (unless that you implement your own mechanism, as they do in Bluebird), but I'm thinking more about at least skipping the ensuing await calls. This post gives some good hints on how to proceed. This usage of a Cancellation Token sent me back to the C#-Tasks world, where Cancellation Tokens are the mechanism used for cancelling tasks. Tasks in .Net are used for 2 different things, IO async operations (where the Task does not involve a new Thread, same as Promises in JavaScript), and CPU intensive operations where you do a Task.Run to run the code in a separate thread and leverage your CPU cores. The rest of this article deals with the Threading and Tasks involving threads" world, not with the IO async operations represented with Promises/Tasks universe.

Cancellation Tokens are a collaborative mechanism. Someone tells the code running in a Thread created via Task.Run or Task.Factory.Start "please stop" and that code is occasionally checking for that kind of requests (through the Cancellation Token) and stops itself. You could use something similar for Threads started via the old Thread.Start mechanism. Use some property, closure variable, whatever shared between the caller and the callee, and have the callee checking for it and quitting if requested.

Threads started with Thread.Start provide you with a more radical cancellation mechanism, Thread.Abort. There's no collaboration here, someone tells the Runtime to stop the thread, and the runtime will do so if certain circumstances are met. Canceling a Thread without its permission can be risky, it can let the system in an unstable state, so some restrictions apply:

  • Thread.Abort will not try to cancel a thread that is running native code. It makes sense, but causes some absurd situation. A thread stopped in a Console.ReadLine() is deep into native code, so it can not be aborted, but this is a pretty harmless operation.
  • The thread is given the chance to manage these abortions. A ThreadAbortException is thrown inside the thread, that can catch it and do some clean up. ThreadAbortExcpetion is a very special exception, even if you catch it, it gets re-thrown, so it will ultimately kill the thread. In order to avoid that, the thread has to call Thread.ResetAbort() inside the catch block.

Related to this, I was revisiting how unhandled exceptions in one thread affect the whole process. An unhandled exception will not just kill the involved thread, but the whole process. This is the same regardless of whether the Thread is foreground or background (background threads are those that will not keep a process running if all the foreground ones are finished). This is not affected either by whether the Thread is a normal thread or a .Net ThreadPool one.

ThreadAbortExceptions are also particular in this sense. As I've said, if we don't call ResetAbort the exception will get to the top of the thread and finish it, but it won't crash the process.

Finally, I've learnt that since .Net 4.5 unhandled exceptions in a Thread started with Task.Run (or Task.Factory.Start) will not tear down the process. I guess Task.Run wraps the call to the provided delegate in a try-catch block.

An additional note, Thread.Abort is not available in .Net Core

No comments:

Post a Comment