Sei sulla pagina 1di 7

08/08/13

.NET 4.0 and System.Threading.Tasks | CodeThinked

CodeThinkedby Justin Etheredge


Pages About Contact Rails 3 Baby Steps Categories Back to Basics Series Blogging Book Review C# Computers Functional Programming Series IronRuby via C# Series Miscellaneous Ruby Scala via C# Series Screencasts Software Design Software Development SquishIt T-SQL Development Technical Presenting Series Technology Uncategorized Web Development RSS

.NET 4.0 and System.Threading.Tasks


www.codethinked.com/net-40-and-systemthreadingtasks 1/14

08/08/13

.NET 4.0 and System.Threading.Tasks | CodeThinked

In the soon-to-be-released .NET 4.0 framework and Visual Studio 2010 we are going to get a plethora of new tools to help us write better multi-threaded applications. One of these tools is a new namespace within the System.Threading namespace which is called "Tasks". The Tasks in System.Threading.Tasks namespace are a method of fine grained parallelism, similar to creating and using threads, but they have a few key differences. The main difference is that Tasks in .NET 4.0 dont actually correlate to a new thread, they are executed on the new thread pool that is being shipped in .NET 4.0. So, creating a new task is similar to what we did in .NET 2.0 when we said: 1 T h r e a d P o o l . Q u e u e U s e r W o r k I t e m ( _= >D o S o m e W o r k ( ) ) ;
?

Okay, so if all we are doing is just plopping a new task on the thread pool, then why do we need this new Task namespace? Well, Im glad you asked! In previous versions of .NET, when we put an item on the thread pool, we had a very hard time getting any information back about what exactly was going on with the piece of work that we had just queued. For example, in the code above, what would we have had to do in order to wait on that piece of work to finish? The thread pool doesnt give us any built-in way to do this, it is just fire and forget. In order to wait, we could have done something like this: 1 2 3 4 5 6 v a rm r e=n e wM a n u a l R e s e t E v e n t ( f a l s e ) ; T h r e a d P o o l . Q u e u e U s e r W o r k I t e m ( _= >{ D o S o m e W o r k ( ) ; m r e . S e t ( ) ; } ) ; m r e . W a i t O n e ( ) ;
?

But that is just a tad bit ugly. And what if we wanted to specify some piece of code that would execute directly after that queued work, and then would use the result? Or what if we wanted to fire off a few pieces of work and then wait for all of them to finish before continuing? Or what if we only wanted to wait for just one of them to finish? What if we wanted to return some value from the piece of work, but block if the result was requested before it was available? What about all of those things? A bit daunting, right? Well, all of this functionality is exactly why Tasks in .NET 4.0 exist!

Creating Tasks
Lets look at how we could create one of these tasks: 1 T a s k . F a c t o r y . S t a r t N e w ( ( )= >D o S o m e W o r k ( ) ) ;
?

Hey, that is pretty simple, and it doesnt look too far removed from throwing items on the thread pool! In fact, when we execute this line, we really are just dropping a task on the thread pool because we arent getting a reference to the task so that we can use its extra functionality! To do this, we could simply assign
www.codethinked.com/net-40-and-systemthreadingtasks 2/14

08/08/13

.NET 4.0 and System.Threading.Tasks | CodeThinked

the result to a variable: 1 v a rt a s k=T a s k . F a c t o r y . S t a r t N e w ( ( )= >D o S o m e W o r k ( ) ) ;


?

This way we now have a reference to the task. So, how is this different from creating a thread again? Well, one of the first advantages of using Tasks over Threads is that it becomes easier to guarantee that you are going to maximize the performance of your application on any given system. For example, if I am going to fire off multiple threads that are all going to be doing heavy CPU bound work, then on a single core machine we are likely to cause the work to take significantly longer. You see, threading has overhead, and if you are trying to execute more CPU bound threads on a machine than you have available cores for them to run, then you can possibly run into problems. Each time that the CPU has to switch from thread to thread causes a bit of overhead, and if you have many threads running at once, then this switching can happen quite often causing the work to take longer than if it had just been executed synchronously. This diagram might help spell that out for you a bit better:

As you can see, if we arent switching between pieces of work, then we dont have the context switches between threads. So, the total cumulative time to process in that manner is much longer, even though the same amount of work was done. If these were being processed by two different cores, then we could simply execute them on two cores, and the two sets of work would get executed simultaneously, providing the highest possible efficiency. Because of this fact, Tasks (or more accurately the thread pool) automatically try to optimize for the number of cores available on your box. However, this is not always the case, sometimes you will fire off threads that will perform actions which require a large amount of waiting. Something like calling a web service, firing off a database query, or simply waiting for some other long running process. With this sort of workload we probably want to execute more than one thread per core. Think about that, if we had 10 different urls that we wanted to download a web page from, we probably dont want to just fire off two at a time on a dual core machine. Since downloading a file from the web isnt very CPU intensive, we probably want to go ahead and fire all of them off at once so that we gain as much as we can from parallel execution. If this was the case, the above task would be executed like this: 1 T a s k . F a c t o r y . S t a r t N e w ( ( )= >D o S o m e W o r k ( ) ,T a s k C r e a t i o n O p t i o n s . L o n g R u n n i n g ) ;
?
3/14

www.codethinked.com/net-40-and-systemthreadingtasks

08/08/13

.NET 4.0 and System.Threading.Tasks | CodeThinked

Again, very easy, all we have to do is tell the task factory that this is a long running task, and it will use a different heuristic to determine how many threads to execute the tasks on.

Waiting On Tasks
Earlier I said that one of the nice features of Tasks was the ability to wait on them easily. In order to do this it is merely a one liner: 1 2 v a rt a s k=T a s k . F a c t o r y . S t a r t N e w ( ( )= >D o S o m e W o r k ( ) ) ; t a s k . W a i t ( ) ;
?

The task will be queued up on the thread pool, and the call to "Wait" will block until its execution is complete. What if we had multiple tasks and we need to wait on all of them. Again, it is a simple one liner: 1 2 3 4 v a rt a s k 1=T a s k . F a c t o r y . S t a r t N e w ( ( )= >D o S o m e W o r k ( ) ) ; v a rt a s k 2=T a s k . F a c t o r y . S t a r t N e w ( ( )= >D o S o m e W o r k ( ) ) ; v a rt a s k 3=T a s k . F a c t o r y . S t a r t N e w ( ( )= >D o S o m e W o r k ( ) ) ; T a s k . W a i t A l l ( t a s k 1 ,t a s k 2 ,t a s k 3 ) ;
?

That sure was hard. And what if we had multiple tasks, and we just wanted to wait for one of them to complete, but we didnt care which one yup, you guessed it, another one-liner: 1 2 3 4 v a rt a s k 1=T a s k . F a c t o r y . S t a r t N e w ( ( )= >D o S o m e W o r k ( ) ) ; v a rt a s k 2=T a s k . F a c t o r y . S t a r t N e w ( ( )= >D o S o m e W o r k ( ) ) ; v a rt a s k 3=T a s k . F a c t o r y . S t a r t N e w ( ( )= >D o S o m e W o r k ( ) ) ; T a s k . W a i t A n y ( t a s k 1 ,t a s k 2 ,t a s k 3 ) ;
?

Again, this task is made very easy by the Task APIs. Earlier I also mentioned something about being able to have a task produce a value, and then block until this value is produced. Well, first we have to look at how we create a task which returns a value. To test this functionality, lets go ahead and create a task that looks like this: 1 2 3 4 5 v a rt a s k=T a s k . F a c t o r y . S t a r t N e w ( ( )= > { T h r e a d . S l e e p ( 3 0 0 0 ) ; r e t u r n" d u m m yv a l u e " ; } ) ;
?

This task is just going to wait a few seconds then return a dummy value. Because the lambda is now returning a value, it is going to use the overload of
www.codethinked.com/net-40-and-systemthreadingtasks 4/14

08/08/13

.NET 4.0 and System.Threading.Tasks | CodeThinked

"StartNew" that takes a Func<T> instead of an Action. So, the task that is produced is now a Task<T> instead of just a Task. The generic parameter T specifies what the type of the result is going to be. The Task<T> type has a property on it called "Result" which will block when we access it. So if we executed the following code, then it would run without incident: 1 2 3 4 5 6 v a rt a s k=T a s k . F a c t o r y . S t a r t N e w ( ( )= > { T h r e a d . S l e e p ( 3 0 0 0 ) ; r e t u r n" d u m m yv a l u e " ; } ) ; C o n s o l e . W r i t e L i n e ( t a s k . R e s u l t ) ;
?

This quite useful! The task is going to execute on a separate thread, and will take 3 seconds. When we call Console.WriteLine though, we wont get an exception because the value is not there, we will simply block and wait until the value is available before continuing on. This can be exceedingly useful when used in conjunction with the long running tasks, since it easily allows us to execute a large number of long running operations and then just ask for their results, knowing that they will simply block until the operations are complete.

Tasks And Continuations


Another really cool feature of Tasks in .NET 4.0 is the ability to create continuations. By this I mean that we can execute a task or a number of tasks and then have a task which will execute after their completion, and even be able to use the result of their execution! It provides a very easy mechanism of coordinating complex thread behaviors. Lets say in the example above, instead of calling "Result" and waiting for it to finish, I could have used a continuation in order to write the value to the console on a separate thread when the task was done executing. In this case, I would not have had any blocking at all, the application would have continued executing, but when the 3 seconds was up, the continuation would be executed and the value would have been written out to the console. The code would look like this: 1 2 3 4 5 T a s k . F a c t o r y . S t a r t N e w ( ( )= > { T h r e a d . S l e e p ( 3 0 0 0 ) ; r e t u r n" d u m m yv a l u e " ; } ) . C o n t i n u e W i t h ( t a s k= >C o n s o l e . W r i t e L i n e ( t a s k . R e s u l t ) ) ;
?

Very powerful. In the example above we are creating the continuation inline, but we could add it on a second line as well: 1 2 v a rt a s k=T a s k . F a c t o r y . S t a r t N e w ( ( )= > {
?

www.codethinked.com/net-40-and-systemthreadingtasks

5/14

08/08/13

.NET 4.0 and System.Threading.Tasks | CodeThinked

3 4 5 6

} ) ; t a s k . C o n t i n u e W i t h ( t= >C o n s o l e . W r i t e L i n e ( t . R e s u l t ) ) ;

T h r e a d . S l e e p ( 3 0 0 0 ) ; r e t u r n" d u m m yv a l u e " ;

We can also do more than just a single continuation, we can chain on any number of continuations: 1 2 3 4 5 6 7 T a s k . F a c t o r y . S t a r t N e w ( ( )= > { T h r e a d . S l e e p ( 3 0 0 0 ) ; r e t u r n" d u m m yv a l u e " ; } ) . C o n t i n u e W i t h ( t= >C o n s o l e . W r i t e L i n e ( t . R e s u l t ) ) . C o n t i n u e W i t h ( t= >C o n s o l e . W r i t e L i n e ( " W ea r ed o n e ! " ) ) ;
?

Continuations provide us with much more rich behavior such as specifying that they should only be executed when an error occurs, when cancellation occurs, we can say that the continuation is long running, we can specify that it is executed on the same thread as its parent, etc There is a lot there, and I encourage you to explore all of the overloads on the "ContinueWith" method. Not only can we perform a continuation on a single task, but we can use static methods on the Task class to allow us to perform continuations on a set of tasks: 1 2 3 4 5 6 7 8 9 1 0 1 1 1 2 1 3 1 4 1 5 1 6 1 7 v a rt a s k 1=T a s k . F a c t o r y . S t a r t N e w ( ( )= > { T h r e a d . S l e e p ( 3 0 0 0 ) ; r e t u r n" d u m m yv a l u e1 " ; } ) ; v a rt a s k 2=T a s k . F a c t o r y . S t a r t N e w ( ( )= > { T h r e a d . S l e e p ( 3 0 0 0 ) ; r e t u r n" d u m m yv a l u e2 " ; } ) ; v a rt a s k 3=T a s k . F a c t o r y . S t a r t N e w ( ( )= > { T h r e a d . S l e e p ( 3 0 0 0 ) ; r e t u r n" d u m m yv a l u e3 " ; } ) ;
6/14

www.codethinked.com/net-40-and-systemthreadingtasks

08/08/13

.NET 4.0 and System.Threading.Tasks | CodeThinked

1 8 1 9 2 0 2 1 2 2 2 3 2 4 2 5

T a s k . F a c t o r y . C o n t i n u e W h e n A l l ( n e w [ ]{t a s k 1 ,t a s k 2 ,t a s k 3} ,t a s k s= > { f o r e a c h( T a s k < s t r i n g >t a s ki nt a s k s ) { C o n s o l e . W r i t e L i n e ( t a s k . R e s u l t ) ; } } ) ;

This way, all tasks will finish, and then we can use each of their results. ContinueWhenAll doesnt block at all, so you might need to add a call to "Wait()" at the end if you are executing inside of a console application.

Summary
This has only been a very light introduction to all of the features that the System.Threading.Tasks namespace gives you in .NET 4.0, but I hope that it has piqued your interest enough that you will want to go spend some time exploring it! Enjoy! Be Sociable, Share!

Tweet

16

Share

This entry was posted on Monday, January 25th, 2010 at 4:05 pm and is filed under Software Development. You can follow any comments to this entry through the RSS 2.0 feed. You can leave a comment, or trackback from your own site.

22 comments
1. Matt Hidinger January 25, 2010 at 6:03 pm This indeed looks like a great set of APIs. Interesting that Ive only just head about it now. Did you happen to have any other good resources while researching this post, or mainly digging around with IntelliSense?
www.codethinked.com/net-40-and-systemthreadingtasks 7/14

Potrebbero piacerti anche