06.06.2010 at
5:02 pm · Saved under
.NET Help
Are you attending TechEd in New Orleans this week? If so, check out the multitude of parallel computing-related breakout sessions, including:
- DEV314 | Manycore and the Microsoft .NET Framework 4 with Microsoft Visual Studio 2010
- DEV317 | Profiling and Debugging Parallel Code with Microsoft Visual Studio 2010
- DEV408 | Task Parallel Library: Design Principles and Best Practices
- ARC205 | Patterns of Parallel Programming
- WSV325 | Technical Computing from Domain Analysis to Performance Profiling
The first two (DEV314 and DEV317) are being presented by Huseyin Yildiz, one of the primary developers on the Parallel Computing Platform team responsible for the Task Parallel Library.
Enjoy!

Tagged: coordination • parallel • parallel-fx • plinq • task-parallel-library • visual-studio-2010
24.05.2010 at
7:24 pm · Saved under
.NET Help
For a relatively advanced feature, I’ve been surprised how often this question has come up recently.
When a task completes, its continuations become available for execution, and by default, a continuation will be scheduled for execution rather than executed immediately. This means that the continuation has to be queued to the scheduler and then later retrieved so that it may be run. Given that there’s some overhead involved there, why would we choose to make that the default behavior rather than avoiding that overhead by executing the continuation synchronously upon completion of the antecedent rather than queueing it? There are a few reasons.
First, it’s quite common for multiple continuations to be created off of the same task. If continuations were executed synchronously by default, we would lose out on a valuable opportunity for parallelism, as they would all be executed synchronously one after the other. By scheduling the continuations to run asynchronously rather than executing them synchronously, we expose those continuations to be picked up by other available threads, thereby allowing them to run in parallel.
Second, it’s quite common for long chains of continuations to be formed, with one task continuing off of another, and another off of that, and another off of that, and so on. If these continuations were all executed synchronously, the completion logic from one task would invoke the next task, and its completion logic would invoke the next… each of these would lead to additional stack frames piling up on top of each other, and with a long enough chain, we could end up overflowing the stack.
Third, a common solution to such overflow conditions as discussed above is to use a ”trampoline,” where you store a reference to some work to be done, back out of your current stack frame(s), and have a higher-level frame (typically a looping construct) look for the stored reference and execute it. That way, after every invocation, rather than picking up the next piece of work immediately, you store the reference, back out, and then execute it. This, as it happens, is exactly the solution TPL employs to make asynchronous execution fast. Remember that as part of .NET 4, the ThreadPool’s internal implementation was augmented with work-stealing queues to which TPL has access. When work running on a ThreadPool thread schedules a Task for execution, that Task is put into a work-stealing queue local to that thread. The thread is able to push and pop work items from it very efficiently and with minimal synchronization. Now, when a task completes, it’s typically completing on a ThreadPool thread, and as such all of the continuations it queues get queued to the local work-stealing queue. The thread will then go in search of work to do, first checking its local queue, and immediately find one of the continuations it just queued. This is, in effect, the trampoline. The thread picks off the most recently queued continuation efficiently and begins processing it.
These are the primary reasons why we default to queueing continuations rather than just executing them synchronously: it provides more opportunities to leverage parallelism, it’s the safer choice, and the difference in performance is typically not important. Of course, microbenchmarks will highlight a non-negligable performance difference, so if you’re dealing with continuations that contain very few instructions, have little risk of blocking, etc., ExecuteSynchronously can be worthwhile. Consider the following simple test:
using System;
using System.Diagnostics;
using System.Threading.Tasks;
class Program
{
const int NUM_CONTINUATIONS = 100000;
static long Test(bool executeSynchronously)
{
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
var first = new Task(() => { });
var last = first;
for (int i = 0; i < NUM_CONTINUATIONS; i++)
{
last = last.ContinueWith(delegate { }, executeSynchronously ?
TaskContinuationOptions.ExecuteSynchronously :
TaskContinuationOptions.None);
}
var sw = Stopwatch.StartNew();
first.Start();
last.Wait();
return sw.ElapsedMilliseconds;
}
static void Main(string[] args)
{
while (true)
{
long withoutExecuteSynchronously = 0, withExecuteSynchronously = 0;
for (int i = 0; i < 5; i++)
{
withoutExecuteSynchronously += Test(false);
withExecuteSynchronously += Test(true);
}
Console.WriteLine((withoutExecuteSynchronously / (double)withExecuteSynchronously).ToString(“F2″));
}
}
}
This test creates a chain of NUM_CONTINUATIONS continuations, each of which does zero work, seeing how long it takes to execute the whole chain, and comparing the cases where the continuations are and are not created with the ExecuteSynchronously option. On the laptop on which I’m writing this blog post, I see the ExecuteSynchronously version running faster, with at most a 2x difference in throughput. This highlights why we made the ExecuteSynchronously option available even though it’s not the default.

Tagged: coordination • parallel-fx • parallelism • visual-studio-2010
15.04.2010 at
7:02 pm · Saved under
.NET Help
Thanks to everyone who attended our three breakout sessions at the Visual Studio 2010 Launch and DevConnections conference this week in Las Vegas.
Attached to this blog post are the slide decks that were presented at the talks. The code from the talks is available either as part of our Parallel Programming in .NET 4 samples at http://code.msdn.microsoft.com/ParExtSamples, or in our whitepaper covering patterns for parallel programming with managed code, available at http://www.microsoft.com/downloads/details.aspx?FamilyID=86b3d32b-ad26-4bb8-a3ae-c1637026c3ee.
Enjoy!
Tagged: parallel • visual • visual-studio-2010
05.12.2009 at
4:59 pm · Saved under
.NET Help
The new parallel debugger windows in Visual Studio 2010 (Parallel Tasks and Parallel Stacks) have had many fixes and updates.
I have refreshed the existing content and also added new material for Beta 2. Find links to all of it from my blog post on Parallel Debugging.
Cheers
Daniel

Tagged: .net • debugging • parallel • tools • visual-studio-2010 • wikipedia
04.11.2009 at
3:52 pm · Saved under
.NET Help
Related posts:
Last time, we covered Tasks being detached by default and some refactorings in our multiple-Task continuation APIs. The final post of this series will discuss Nested Tasks and Unwrap, a Parallel namespace change, and some changes under the covers.
Nested Tasks and Unwrap
We’ve added the Unwrap APIs to address scenarios that deal with nested Tasks. Before jumping into Unwrap, let’s first talk about nested Tasks, e.g. a Task<Task> or Task<Task<TResult>>. For example:
var nestedTask = Task.Factory.StartNew(() =>
{
return Task.Factory.StartNew(() =>
{
return 42;
});
});
Nested Tasks commonly lead to unexpected behavior in applications. For example, consider an API that provides the following functionality for asynchronously logging into a web service (like one from Facebook), retrieving a list of friends, and sending an email to each friend.
// Given a user name and password, returns a Task whose
// result is a UserToken object.
public Task<UserToken> LogOn(string username, string password);
// Given a UserToken, returns a Task whose result
// is a collection of Friend objects.
public Task<FriendCollection> GetFriendList(UserToken userToken);
// Given a Friend, subject, and body, returns a Task that
// represents an email sending operation.
public Task SendEmail(Friend friend, string subject, string body);
A user would like to be able to write code like the following, utilizing these APIs and Task continuations:
var userToken = LogOn(user, pass);
var friends = userToken.ContinueWith(_ => GetFriendList(userToken.Result));
var emails = friends.ContinueWith(_ =>
{
var sentMails = new List<Task>();
foreach(var friend in friends.Result)
{
sentMails.Add(SendEmail(friend, subject, body));
}
return Task.Factory.ContinueWhenAll(
sentMails.ToArray(), tasks => Task.WaitAll(tasks));
});
emails.ContinueWith(_ => Console.WriteLine(“All emails sent”));
The bolded code is problematic. Because the GetFriendList method returns a Task<FriendCollection>, the ‘friends’ variable is actually going to be a Task<Task<FriendCollection>>. This will cause a compiler error at the foreach loop, because ‘friends.Result’ will return a Task<FriendCollection> instead of a FriendCollection. The compiler error in this case is a good thing, of course, highlighting a programming error. However, once a developer realizes the type mismatch, he still has to code additional logic to unwrap the ‘friends’ Task so that it returns an actual FriendCollection. This logic is nontrivial, especially if it is to correctly deal with exceptions, cancellation, etc. The last line is also problematic. The emails variable here is actually of type Task<Task>. The outer Task will complete once the inner Task is returned from its body, even if the inner Task hasn’t completed. The net result of this is that “All emails sent” could be written out prior to all of the email tasks actually completing.
Now, you may have noticed that, in Beta 1, we provided special ContinueWith overloads to deal with this type of scenario. For example:
public Task<TResult> ContinueWith<TResult>(
Func<Task, Task<TResult>> continuationFunction);
The Func returns a Task<TResult>, so normally, ContinueWith would return a Task<Task<TResult>>. But this ContinueWith overload does some magic under the covers to return a Task<TResult>. There were a number of reasons why we didn’t like this approach, including:
· Too much magic. It’s hard to explain why one set of ContinueWith overloads is “just different”.
· Only works for ContinueWith. What if user scenarios result in nested Tasks for other Task creation APIs like StartNew, ContinueWhenAll, etc.?
· What if nested Tasks are actually desired? If a user actually wants that Task<Task<TResult>>, he still might unknowingly bind to this magical overload.
Given these reasons, our solution for Beta 2 and beyond is two Unwrap extension methods.
namespace System.Threading.Tasks
{
public static class TaskExtensions
{
public static Task Unwrap(
this Task<Task> task);
public static Task<TResult> Unwrap<TResult>(
this Task<Task<TResult>> task);
}
}
These methods may be used to transform any Task<Task> or Task<Task<TResult>> into a Task or Task<TResult>, respectively. The transformation performed produces a Task or Task<TResult> that fully represents the original nested Task, including exceptions, cancellation state, etc.
With Unwrap, we can fix the above scenario (note the bolded).
var userToken = LogOn(user, pass);
var friends = userToken.ContinueWith(
_ => GetFriendList(userToken.Result)).Unwrap();
var emails = friends.ContinueWith(_ =>
{
var sentMails = new List<Task>();
foreach(var friend in friends.Result)
{
sentMails.Add(SendEmail(friend, subject, body));
}
return Task.Factory.ContinueWhenAll(
sentMails.ToArray(), tasks => Task.WaitAll(tasks));
}).Unwrap();
emails.ContinueWith(_ => Console.WriteLine(“All emails sent”));
Parallel Namespace Change
We’ve moved the Parallel class from the System.Threading namespace to the System.Threading.Tasks namespace. We found that most applications needed to bring in both namespaces when using TPL, so why not put everything into one namespace? Additionally, Parallel is such a common word (and will likely become more so in the future), and System.Threading such a common namespace, we wanted to reduce the chances of conflict with other .NET types as much as possible.
Here’s a useful IDE tip. Use “Ctrl + .” to automatically bring in the relevant namespace once you’ve typed a class/type name.

Under the Covers
Beta 2 contains quite a few bug fixes not done for Beta 1. All of them were important, but we’ll focus on only two here.
First, Parallel.For and ForEach have been tweaked for better load-balancing with other workloads in the current or other AppDomains. Essentially, the change was to service the parallel loops with Tasks that periodically retired and re-queued themselves, allowing other contenders to grab Threads and make progress.
Second, waiting for Tasks in parent/child relationships has become more efficient. In Beta 1 and before, parent Tasks waited for their children using explicit Waits, so even if a parent completed first, it would burn a thread until all of its children completed. In Beta 2, parent Tasks wait for their children using callbacks; the parent maintains a count for number of children it has, and each child decrements the count as it completes. This waiting logic significantly improves scalability.
That’s it folks! We hope you’ve enjoyed this series. Download Beta 2 and try it out!
Tagged: beta • parallel • task-parallel-library • visual-studio-2010
27.10.2009 at
1:02 pm · Saved under
.NET Help
Related posts:
· What’s new in Beta 2 for the Task Parallel Library (1/3)
Last week, we talked about how TPL adopted a new, better cancellation model. Today, we’ll cover a change that makes Tasks Detached by Default, some ContinueWhenAll/Any Refactoring, and the handy UnobservedTaskException event.
Tasks are Detached by Default
In Beta 2, we have changed an important default. Tasks are now created as detached (instead of attached) if no options specify otherwise.
Let’s consider the following code to review the difference between attached and detached Tasks.
Task p = Task.Factory.StartNew(() =>
{
Task c = Task.Factory.StartNew(() =>
{
DoWork();
});
});
p.Wait();
In Beta 1 and before, since the default options are used, ‘c’ is created as a child Task of Task ‘p’, the parent Task; we refer to this as Task ‘c’ being “attached” to Task ‘p’. This means that the p.Wait() statement will not return until the call to DoWork completes, because parent Tasks do not complete until all of their child Tasks complete. To opt out of this behavior, a user needs to create ‘c’ with the DetachedFromParent option:
Task c = Task.Factory.StartNew(() =>
{
DoWork();
}, TaskCreationOptions.DetachedFromParent);
The original code shown behaves differently in Beta 2. Now, by default, ‘c’ is not related to ‘p’ (it’s “detached” by default), and the p.Wait() statement will return as soon as ‘p’ completes, regardless of the status of Task ‘c’ and thus regardless of when DoWork returns. To opt in to the parent/child relationship, a user needs to create ‘c’ with the AttachedToParent option:
Task c = Task.Factory.StartNew(() =>
{
DoWork();
}, TaskCreationOptions.AttachedToParent);
Here is a summary of the changes:
· Removed the DetachedFromParent option
· Added the AttachedToParent option
· Changed the default behavior so that Tasks do not enlist in parent/child relationships when no options are specified.
There were a number of reasons why we decided that detached is the correct default and to move forward with this change, including:
· Many users were using attached Tasks unknowingly. The vast majority of the time, users create Tasks for simple, asynchronous work. In such scenarios, parent/child relationships (and the implicit waiting) are not needed. We found though many interactions that folks were just going with the default options and were accidentally opting in to this behavior. In the best case, this would only result in a slight performance cost. In the worst case, this would bring with it incorrect behavior that would lead to difficult to diagnose errors.
· Easier migration from ThreadPool.QueueUserWorkItem. Tasks are now the recommended way to queue work to the ThreadPool, but the easiest way to create Tasks resulted in different behavior from QueueUserWorkItem (where there’s no concept of parent/child work items). This change makes Task.Factory.StartNew (with no options) a true replacement for QueueUserWorkItem.
· Additional behavior should be opt-in and pay-for-play. Almost everything in TPL that results in additional behavior is opt-in, e.g. cancellation, LongRunning, PreferFairness. With the Beta 1 default, users opt-out of parent/child relationships. In Beta 2, users opt-in, making it consistent. This makes the extra functionality provided by parent/child relationships pay-for-play, such that you don’t pay the cost for parents implicitly waiting for their children or for exceptions propagating from children to parents unless you need that functionality.
ContinueWhenAny/All Refactoring
We have refactored the set of ContinueWhenAny and ContinueWhenAll overloads to make things more intuitive, consistent, and complete.
To demonstrate the main issue, let’s consider the following overload that was provided in Beta 1.
public class TaskFactory<TResult>
{
public Task<TNewResult> ContinueWhenAny(
Task<TResult>[] tasks,
Func<Task<TResult>, TNewResult> continuationFunction);
}
This confused the meaning of TaskFactory<TResult>, which is meant to create tasks of type Task<TResult>. However, with these overloads, TaskFactory<TResult> could be used to create tasks of type Task<TNewResult>. As an example, consider the code:
Task<int>[] taskOfInts = …;
Task<string> t = Task<int>.Factory.ContinueWhenAll(taskOfInts, _ => “”);
This compiles and works just fine, but the type parameter mismatch (shown in bold) is certainly odd. To address this, we changed a bunch of overloads, so that instead of taking Task<TResult>s and returning a Task<TNewResult>, they take Task<TAntecedentResult>s and return Task<TResult>s. For example, the overload that replaced the above is:
public Task<TResult> ContinueWhenAny<TAntecedentResult>(
Task<TAntecedentResult>[] tasks,
Func<Task<TAntecedentResult>, TResult> continuationFunction);
And the above example becomes:
Task<int>[] taskOfInts = …;
Task<string> t = Task<string>.Factory.ContinueWhenAll(taskOfInts, _ => “”);
In addition to this change, we also added, removed, or modified a number of other overloads to make the set consistent and complete. Now, the entire set of ContinueWhenAll and ContinueWhenAny overloads follow these clear rules:
· A TaskFactory creates Tasks, but also provides overloads to create Task<TResult>s.
· A TaskFactory<TResult> only ever creates Task<TResult>s (never Tasks or Task<TNewResult>s).
UnobservedTaskException event
We’ve added an event that fires for every Task exception that goes unobserved. Recall that to “observe” a Task’s exceptions, you must either Wait on the Task or access its Exception property after it has completed. At least one of these actions must be done before the Task object is garbage collected, or its exceptions will propagate (currently this occurs on the finalizer thread).
The new static event resides on the TaskScheduler class, and subscribing to it is straightforward. Here’s an example to log all unobserved exceptions and mark them as observed (preventing them from being propagated).
TaskScheduler.UnobservedTaskException +=
(object sender, UnobservedTaskExceptionEventArgs exceptionArgs) =>
{
exceptionArgs.SetObserved();
LogException(exceptionArgs.Exception);
};
Some customers have complained that TPL’s exception policy is too strict. The UnobservedTaskException event provides an easy way out by allowing you to simply squash all Task exceptions in an application (though using it in this manner is not recommended). The primary reason that we made the addition was to support host-plugin scenarios where a host application can still be perfectly useful in the presence of some truly harmless exceptions (thrown by buggy plugins). These scenarios may be achieved using the UnobservedTaskException event in conjunction with AppDomains to sandbox plugins. Look for a future post that describes this in more detail!
We’re done for now! The 3rd and final post of this series will cover the new Unwrap APIs, a Parallel namespace change, and some changes under the covers.
Tagged: beta • task-parallel-library • visual-studio-2010
19.10.2009 at
1:50 pm · Saved under
.NET Help
The .NET Framework 4 Beta 2 is now available!
MSDN Subscribers can download it today, and it will be generally available for download on Wednesday. More information is available at http://msdn.microsoft.com/en-us/vstudio/dd582936.aspx. Additionally, one of the really exciting things about this Beta release is that it’s “go-live”; more information on that is available at “Going live” with Visual Studio 2010 Beta 2.
As part of .NET 4 Beta 2, of course, comes improved parallelism support. In the coming days and weeks, we’ll be blogging here about some of the exciting updates and additions in this release. Stay tuned…
In the meantime, the MSDN documentation on .NET 4’s parallelism support has been updated. It’s a great place to start learning about all of the functionality available in this release.
You can also learn more about the tools in Visual Studio 2010 for parallel development in the documentation at:
Enjoy!
![.NET 4 Beta 2 is here! .NET 4 Beta 2 is here!]()
Tagged: parallel-extensions • release • tools • visual-studio-2010 • wikipedia
16.10.2009 at
10:49 am · Saved under
.NET Help
In a week, I’m going to be traveling through Michigan, Ohio, Kentucky, and Tennessee, speaking about parallel computing, Visual Studio 2010, and .NET 4, primarily at corporations during the day and at user groups in the evenings.
If you’re in the area and interested, please do attend, and I look forward to meeting you! A list of events is available at http://blogs.msdn.com/jennifer/archive/2009/10/14/stephen-toub-parallel-computing-tour.aspx.
See you in a week!
Stephen
![Parallel Computing Presentations in Michigan, Ohio, Kentucky, and Tennessee Parallel Computing Presentations in Michigan, Ohio, Kentucky, and Tennessee]()
Tagged: default-aspx • parallel • presentations • talks • visual-studio-2010
22.09.2009 at
11:46 am · Saved under
.NET Help
The Task abstractions in .NET 4 run on instances of the TaskScheduler class. Two implementations of TaskScheduler ship as part of the .NET Framework 4. The first is the default scheduler, which is integrated with the .NET 4 ThreadPool and takes advantage of its work-stealing queues. The second is the type of TaskScheduler returned from the static method TaskScheduler.FromCurrentSynchronizationContext.
According to MSDN, SynchronizationContext “provides the basic functionality for propagating a synchronization context in various synchronization models.” What does that really mean? At its core, SynchronizationContext provides two methods, Send and Post, both of which accept a delegate to be executed. Send synchronously invokes the delegate, and Post asynchronously invokes the delegate. That’s it, and the base implementation of SynchronizationContext doesn’t do anything fancier than that:
public virtual void Send(SendOrPostCallback d, object state)
{
d(state);
}
public virtual void Post(SendOrPostCallback d, object state)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(d.Invoke), state);
}
Where things get interesting is when new types are derived from SynchronizationContext, something typically done by a UI framework (though there are other non-UI framework implementations). For folks familiar with Windows Forms and Windows Presentation Framework development, you’re likely aware that UI controls should only be accessed by the thread that created them, almost always the main UI thread. Thus, if a thread doing work in the background wants to update something in the UI, it needs to marshal that work back to the GUI thread so that the controls may be accessed safely. Different UI frameworks expose different ways for accomplishing this marshaling. For example, in Windows Forms, one uses the Invoke or BeginInvoke method of the target Control (or at least a Control created on the same thread as the target Control). In WPF, one uses the target thread’s Dispatcher and corresponding Invoke/BeginInvoke methods. With every UI framework having its own model for marshaling work to a particular “synchronization context”, it becomes difficult to write code that supports this marshaling concept but which is agnostic to the particular environment that it’s in. Enter SynchronizationContext. A new type may be derived from SynchronizationContext such that its Send method synchronously marshals a delegate to the right thread for execution, and Post does the same but asynchronously. If you look at the implementations of the SynchronizationContexts provided by Windows Forms and WPF, that’s exactly what they do, delegating to the relevant Invoke/BeginInvoke methods from Send and Post to marshal the work correctly.
To make it easy to get at the right SynchronizationContext, a UI framework like Windows Forms will publish an instance of its SynchronizationContext-derived class to SynchronizationContext.Current. Code can then grab SynchronizationContext.Current and use it to marshal work, without having to know whether it’s being used from Windows Forms or Windows Presentation Foundation or another similar model.
“TaskScheduler.FromCurrentSynchronizationContext” should now make more sense. This method creates a TaskScheduler that wraps the SynchronizationContext returned from SynchronizationContext.Current. Thus, this gives you a TaskScheduler that will execute Tasks on the current SynchronizationContext. Why is that useful? It means you can create Tasks that are able to access UI controls safely, simply by running them on the right scheduler.
Let’s say that I wanted to load three images from some data source. When those images have been loaded, I want to blend them all together, and then I want to display the result into a PictureBox on my UI. Using Tasks and TaskScheduler.FromCurrentSynchronizationContext, I could write code like the following:
private void Button1_Click(…)
{
var ui = TaskScheduler.FromCurrentSynchronizationContext();
var tf = Task.Factory;
// Load the three images asynchronously
var imageOne = tf.StartNew(() => LoadFirstImage());
var imageTwo = tf.StartNew(() => LoadSecondImage());
var imageThree = tf.StartNew(() => LoadThirdImage());
// When they’ve been loaded, blend them
var blendedImage = tf.ContinueWhenAll(
new [] { imageOne, imageTwo, imageThree }, _ =>
BlendImages(imageOne.Result, imageTwo.Result, imageThree.Result));
// When we’re done blending, display the blended image
blendedImage.ContinueWith(_ =>
{
pictureBox1.Image = blendedImage.Result;
}, ui);
}
This code runs three tasks to load the three input images asynchronously. When all of those have been loaded, again asynchronously some BlendImages method is used to blend the images, taking the three inputs and returning the blended image. Finally, once that’s done, another task is used to render the blended image by storing it into a PictureBox on the UI. Since this modifies a UI control, we need to do it from the UI thread. Thus, we pass a TaskScheduler to the ContinueWith method; this scheduler targets the UI’s SynchronizationContext, and will cause the Task to execute on the UI thread.
TaskScheduler.FromCurrentSynchronizationContext is provided for convenience and because this is a very common need. However, due to TaskScheduler’s extensibility, it’s actually possible to implement this behavior yourself, and in doing so you could modify it to suit your own needs however you see fit.
Let’s say you did want to develop a new SynchronizationContextTaskScheduler. It might look something like this:
public class SynchronizationContextTaskScheduler : TaskScheduler
{
private ConcurrentQueue<Task> _tasks = new ConcurrentQueue<Task>();
private SynchronizationContext _context;
public SynchronizationContextTaskScheduler() :
this(SynchronizationContext.Current) { }
public SynchronizationContextTaskScheduler(
SynchronizationContext context)
{
if (context == null) throw new ArgumentNullException(”context”);
_context = context;
}
protected override void QueueTask(Task task)
{
// Add the task to the collection
_tasks.Enqueue(task);
// Queue up a delegate that will dequeue and execute a task
_context.Post(delegate
{
Task toExecute;
if (_tasks.TryDequeue(out toExecute)) TryExecuteTask(toExecute);
}, null);
}
protected override bool TryExecuteTaskInline(
Task task, bool taskWasPreviouslyQueued)
{
return SynchronizationContext.Current == _context &&
TryExecuteTask(task);
}
public override int MaximumConcurrencyLevel { get { return 1; } }
protected override IEnumerable<Task> GetScheduledTasks()
{
return _tasks.ToArray();
}
}
Not a lot of code for a fairly powerful thing.
- The constructors simply accept the target SynchronizationContext and store it, also initializing a thread-safe queue that will store the tasks to be executed.
- The QueueTask method is called whenever the system is providing a Task for this scheduler to execute: this scheduler handles it by storing that Task into a queue, and then Post’ing to the SynchronizationContext a delegate that will pull the next Task from the queue and execute it.
- The TryExecuteTaskInline is invoked any time the system wants to run a Task inline on the current thread (either from a call to RunSynchronously or from a Wait attempt): we need to make sure that the call is coming from the same SynchronizationContext as the target, otherwise we may end up running in the wrong context.
- We only intended to support SynchronizationContexts that represent a single thread of execution (that’s most common), so we return 1 from MaximumConcurrencyLevel.
- And we want the debugger to be able to display tasks scheduled to this scheduler, so we override GetScheduledTasks to return an array of the tasks queued. (This is why we need to explicitly store the queue of tasks; otherwise, we could have simply relied on lambda closures to capture each task to be executed.)
![Task Scheduler From Current Synchronization Context]()
Tagged: code-samples • contests • parallel-extensions • queue • silverlight • target • task-parallel-library • threadpool • tools • visual-studio-2010 • wikipedia • windows-forms