As would say this guy, since you’ve most probably been using threads the wrong way (as Microsoft seems to think), you won’t be able to use the Thread class anymore in Metro Style applications. The class is simply not available anymore, and neither are Timer or ThreadPool.
That may come a shock to you, but this actually makes a lot of sense. But don’t worry, the concept of parallel execution is still there, but it takes the form of Tasks.
Why using Threads is not good for you
Threads are very powerful but there are a lot of terrible gotchas that come with it :
- Unhandled exceptions in threads handlers, either raised from a Timer, a Thread or ThreadPool thread, lead to the termination of the process
- Using Abort is quite bad for the process, and should be avoided
- People tend to use Thread.Sleep to arbitrarily wait for some constant time that will most probably be incorrect, and that will waste CPU resources to manage a thread that does not do anything while it waits,
- People tend to come up with complex designs to chain operations on threads, which most of the time fail miserably.
There are some more, but these a main scenarios where using Threads fall short.
I’ve been advocating to stay away from Threads, at least not directly, for all these reasons (and more, but that’s out of scope here).
Using Task, exclusively
Since Microsoft went back to rethink some patterns that were introduced in the original BCL and CLR, they probably thought it was time to time to remove the Thread class in favor of the Task class, which does a far better job, and handles all the cases I listed above :
- Tasks now handle exceptions properly, forwarding the exception to the code that receives the result of that task,
- It’s now possible to use the CancellationTokenSource class to handle cancellation in a very nice way,
- Even though it’s a sign of poorly designed program, Task.Delay is the new replacement for Thread.Sleep.
- There are methods like Task.WaitAny,Task.WhenAll or Task.ContinueWith that allow for very safe task chaining operations.
All these operations blend very nicely into the new async feature, for which it is very easy to wait on a Task.
ThreadPool and Timer moved to WinRT
The Thread Pool is still there actually, and so is the timer in the form of the class TheadPoolTimer, but they’ve both moved to the WinRT side.
ThreadPool is async awaitable, and supports priority, much like Task. As of now, I do not see a very good compelling reason to use it, since Task has a far greater feature set.
ThreadPoolTimer can still be interesting, though exceptions thrown in this context seem to be handled silently by WinRT, and I’ve yet to find where that goes. But I’d recommend not using it, in favor of theReactive Extensions' Observable.Timer which far more useful than this simple timer.
Thread left-overs
There’s actually one place where the “Threads” still surface in the BCL.
If we take this C# code :
1
2
3
4
| public IEnumerable IteratorSample() { yield return 1; } |
One feature of the compiler generated iterators is that they are explicitly not thread safe, and must be used on the thread they were generated on. The iterator is capturing the original thread, and is checking that subsequent calls stay on that thread.
So if we look at the what is internally expanded to an internal iterator class, using the C# compiler on .NET 4.0 and earlier :
1
2
3
4
5
6
| [DebuggerHidden] public d__0( int <>1__state) { this .<>1__state = <>1__state; this .<>l__initialThreadId = Thread.CurrentThread.ManagedThreadId; } |
Whereas, using the .NET 4.5 C# compiler, this will be generated :
1
2
3
4
5
6
| [DebuggerHidden] public d__0( int <>1__state) { this .<>1__state = <>1__state; this .<>l__initialThreadId = Environment.CurrentManagedThreadId; } |
The .NET 4.5 generated code is making use of the new Environment.CurrentManagedThreadId property, because that specific iterator feature needs to have access to the actual thread ID, even though the Thread class does not exist anymore.
Interesting, isn’t it ?
This has a very unfortunate effect, though. C# compiled by a compiler below .NET 4.5 is not binary compatible with the Metro Style apps BCL, and will not run without being recompiled. But that’s not a big deal, because most the .NET surface APIs have either changed (to be async only), moved (like the Reflection API) or simply removed (like the System.IO namespace that went into WinRT), so you would have to adapt your code anyway.
I’m guessing that the C# team had to fight for this feature to maintain compatibility and have the same behavior as in previous versions of C#. And I’m glad this property stayed, because I’ve been using it to log the ThreadID in my logging framework.
Happy WinRT'ing !
No comments:
Post a Comment