Multi threaded design guidelines for librariesPart III
In this post, I want to talk about libraries that want or need to not only support being run in multiple threads, but actually want to use multiple threads themselves. Remember, you are a library, not a framework. You are a guest in someone’s else home, and you shouldn’t litter.
The first thing to remember is error handling. That actually comes in two parts. First, unhandled exceptions from a thread will kill the application. There are very few things that people will find more annoying with your library than your errors killing their application. Second, and almost as important, you should have a way to report those errors.
Even more annoying than killing my application, failing to do something silently and in a way that is really hard to debug is going to cause major hair loss all around.
There are several scenarios that we need to consider:
- Long running threads – I need to do something in a background thread that would usually live as long as the application itself.
- Short term threads – I need to do something that requires a lot of threads, just for a short time.
- Timeouts / delays / expirations – I need to do something every X amount of time.
In the first case, of long running threads, there isn’t much that can be done. You want to handle errors, obviously, and you want to make it crystal clear when you spin up your threads, and when / how you tear them down again. Another important aspect is that you should name your threads. This is important because it means that when debugging things, we can figure out what this or that thread is doing more easily.
The next approach is much more common, you just need some way to execute some code in parallel. The easiest thing to do is to go to new Thread(), ThreadPool.QueueUserWorkItem or Task.Factory.StartNew(). Such, this is easy to do, and it is also perfectly wrong.
Why is that, you say?
Quite simply, it ain’t your app. You don’t get to make such decisions for the application that is hosting your library. Maybe the app needs to conserve threads to serve requests? Maybe it is trying to utilize less threads to reduce CPU load and save power on a laptop running on batteries? Maybe they are trying to debug something and all those threads popping around is driving them crazy?
The polite thing to do when you recognize that you have a threading requirement in your application is to:
- Give the user a way to control that.
- Provide a default implementation that works.
A good example of that can be seen in RavenDB’s sharding implementation.
public interface IShardAccessStrategy { event ShardingErrorHandle<IDatabaseCommands> OnError; T[] Apply<T>(IList<IDatabaseCommands> commands, ShardRequestData request, Func<IDatabaseCommands, int, T> operation); }
As you can see, we abstracted the notion of making multiple requests. We provide you out of the box with sequential and parallel implementations for this.
The last item, timeouts /expirations / delays is also something that you want to give the user of your library control of. Ideally, using something like the strategy above. By all means, make a default implementation and wire it without needing anything.
But it is important to have control over those things. The expert users for your library will want and need it.
Comments
The last sentence ... so true. How many times hours are burnt with decompiling, rev engineering to find out how to influence a library ...
Cheers, Miklos
ssssssssssssss
Comment preview