Actually it’s ten things you must know about .Net, but that sounds a bit pushy. This is not a "12 steps to better code" collection, we’ll be clearing up a lot common misconceptions about the .Net programming in general. Most of the new generation of coders grew up and got into creating production code without even seeing Assembly code or C, without having real understanding of what happens outside their code’s boundaries. I’m one of these guys. Let’s get started with today’s favorite, threading.
Just don't. Multi threading is a nice feature, it is the basic of rich client applications and maximized performance on multi core systems, but only if you understand what happens behind the scenes. There are a lot of common mistakes, some might hurt your application only a little, others may cause your web application to crash on a regular basis.
Threads are valuable resources. Creating and destroying threads is managed by the operating system and these are real expensive cycles, not to mention the allocation and teardown of the 1MB stack allocated for each thread. But lucky for us, each managed application has an own thread pool, that can be used to execute operations asynchronous. This thread pool holds up to a maximum default amount of 250 threads per processor core, but a minimum of 1 thread per core. These threads are background threads, so if the application dies or exits, the CLR will clean them up, and free managed/unmanaged resources utilized by them. You don’t have direct access to the thread pool – you cannot pre-determine which thread will process your queued item. If your application queues more work items, than the current amount of threads in it’s threadpool can process, CLR calls down to the OS level, and allocates a few more for you. These threads wont be destroyed after the load falls back, making your application more responsive to new peeks. Some might think a directly instantiated thread means better control, but actually the following code part in 90% indicates a major design flaw.
Thread thread = new Thread(Accept); thread.Start();
And now let’s see two of the most common cases coders tend to type these two lines of sorrow.
Like clearing the cache. Feeding the dog. Setting workset size via native winapi call to hide the memory leak in early flash activeX component. The most common misconception of threading is timing an event in the background. It does seem a lot more pro than a simple timer, and this is the bear trap…
Thread thread = new Thread(ClearCache); thread.Start();
private void ClearCache() { //Do something, that looks like sophisticated business logic Thread.Sleep(10000); }
Bang. Let’s see the flaws:
System.Timers.Timer is a lightweight component, that fires it’s event into the thread pool. This ensures, that accuracy is not jeopardized by execution time – or even worse: timeout, it is not affected by exception on the executing thread, and does not litter the memory with a 1 MB stack for nothing.
Timer workTimer = new Timer(ClearCache, null, 10000, 10000);
private void ClearCache(object state) { //Do something time consuming }
Winforms/XAML based applications have only one thread communicating with the OS, this pumps the window messages that you can hook to, or just simply watch them amazed using Spy++. If this thread is blocked, when windows queries the window for it’s state, it receives no answer. No redraws will be made, windows flags the application as “Not responding”, and the white form of death will put it’s shadow on your soul. Cheap software taste, we don’t want that. So we have our data query method collecting data from 4 different databases, and this takes an average of 10 seconds time to accomplish.
Well, you could do that. And it would only hurt the application a bit. But why would you spend time creating a dispatcher, a queue, sharing variables to do this whole stuff optimal, when you can call any method you want Asynchronous, as async framework is part of the .Net framework? This gets to us to our next answer:
Async Delegates are cool. Especially when you have to migrate some legacy code into multi threaded environment. I would have wrote a few lines about, but there is no reason for it, as Richard Grimes already wrote a real nice article in this topic. Go and see for yourself. But this is not a two options decision, this is where the applications own thread pool comes in:
It’s simple:
ThreadPool.QueueUserWorkItem(Calculate);This will register the delegate and the first available thread in the pool will process it.
Use multi threading. Create rich client applications, complex multitasking systems, but every time you want to create a thread, just think out of the box.