{"id":55929,"date":"2010-04-07T10:00:00","date_gmt":"2010-04-07T10:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/pfxteam\/2010\/04\/07\/parallelextensionsextras-tour-5-stataskscheduler\/"},"modified":"2010-04-07T10:00:00","modified_gmt":"2010-04-07T10:00:00","slug":"parallelextensionsextras-tour-5-stataskscheduler","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/dotnet\/parallelextensionsextras-tour-5-stataskscheduler\/","title":{"rendered":"ParallelExtensionsExtras Tour &#8211; #5 &#8211; StaTaskScheduler"},"content":{"rendered":"<p class=\"MsoNormal\"><font size=\"3\"><font face=\"Calibri\"><em>(The full set of ParallelExtensionsExtras Tour posts is available&nbsp;<\/em><a href=\"https:\/\/blogs.msdn.com\/pfxteam\/archive\/2010\/04\/04\/9990342.aspx\"><font color=\"#dd4a21\"><em>here<\/em><\/font><\/a><em>.)<\/em>&nbsp;&nbsp;<\/font><\/font><\/p>\n<p class=\"MsoNormal\"><font size=\"3\"><font face=\"Calibri\">The Task Parallel Library (TPL) supports a wide array of semantics for scheduling tasks, even though it only includes two in the box (one using the ThreadPool, and one using SynchronizationContext, which exists primarily to run tasks on UI threads).<span>&nbsp; <\/span>This support comes from the extensibility of the abstract TaskScheduler class, which may be derived from to implement custom scheduling algorithms.<span>&nbsp; <\/span><\/font><\/font><a href=\"https:\/\/code.msdn.microsoft.com\/ParExtSamples\"><font size=\"3\" face=\"Calibri\">ParallelExtensionsExtras<\/font><\/a><font size=\"3\"><font face=\"Calibri\"> includes a multitude of such derived types, and in our tour through ParallelExtensionsExtras, we&rsquo;ll walk through a few of them&#8230; today, we&rsquo;ll start with StaTaskScheduler.<\/p>\n<p><\/font><\/font><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">In an ideal world, there would be no legacy code; all code would be up-to-date on best practices and would be using the latest and greatest&nbsp;technologies.<span>&nbsp; <\/span>In the real world, that&rsquo;s almost never possible, and we need to be able to accommodate designs of the past.<span>&nbsp; <\/span>Single-threaded apartments (STA) from COM represent one such technology we need to be able to integrate with (rather than describing STA here, for more information see <\/font><a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms680112(VS.85).aspx)\"><font color=\"#0000ff\" size=\"3\" face=\"Calibri\">http:\/\/msdn.microsoft.com\/en-us\/library\/ms680112(VS.85).aspx)<\/font><\/a><font size=\"3\"><font face=\"Calibri\">.<span>&nbsp; <\/span>If a components is STA, it must only be used from an STA thread.<span>&nbsp; <\/span>However, all of the ThreadPool&rsquo;s threads are multi-threaded apartment (MTA) threads, and by default Tasks from TPL run on these threads.<span>&nbsp; <\/span>The good news is that TPL&rsquo;s implementation is able to run on either MTA or STA threads, and takes into account relevant differences around underlying APIs like WaitHandle.WaitAll (which only supports MTA threads when the method is provided multiple wait handles).<span>&nbsp; <\/span>Given that, we can take advantage of the TaskScheduler API to implement an StaTaskScheduler which runs tasks on STA threads.<span>&nbsp; <\/span>This then allows you to use Tasks with STA components.<span>&nbsp; <\/span>StaTaskScheduler is implemented in the StaTaskScheduler.cs file in ParallelExtensionsExtras.<\/p>\n<p><\/font><\/font><\/p>\n<p class=\"MsoNormal\"><font size=\"3\"><font face=\"Calibri\">Our scheduler will derive from TaskScheduler and will also implement IDisposable in order to enable shutdown of the STA threads being used by the scheduler.<span>&nbsp; <\/span>It will contain two fields: a List&lt;Thread&gt; for storing the threads created and used by the scheduler, and a BlockingCollection&lt;Task&gt; for storing all tasks scheduled to the scheduler, enabling the scheduler&rsquo;s threads to consume and process them.<\/p>\n<p><\/font><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">public sealed class StaTaskScheduler : TaskScheduler, IDisposable<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">{<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\"><span>&nbsp;&nbsp;&nbsp; <\/span>private readonly List&lt;Thread&gt; _threads;<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\"><span>&nbsp;&nbsp;&nbsp; <\/span>private BlockingCollection&lt;Task&gt; _tasks;<\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">&nbsp;&nbsp;&nbsp; &#8230;<\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">}<\/p>\n<p><\/font><\/p>\n<p class=\"Code\">\n<p><font face=\"Consolas\">&nbsp;<\/font><\/p>\n<\/p>\n<p class=\"MsoNormal\"><font size=\"3\"><font face=\"Calibri\">When created, the scheduler will instantiate the BlockingCollection&lt;Task&gt; and spin up numberOfThreads threads, where numberOfThreads is provided to the constructor of the type.<span>&nbsp; <\/span>Each of the threads is explicitly marked with ApartmentState.STA.<span>&nbsp; <\/span>The thread procedure for each thread is a simple dispatch loop that continually takes from the BlockingCollection&lt;Task&gt;<span>&nbsp;<\/span>(blocking if the collection is empty) and executes the discovered Task.<\/p>\n<p><\/font><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">public StaTaskScheduler(int numberOfThreads)<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">{<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\"><span>&nbsp;&nbsp;&nbsp; <\/span>if (numberOfThreads &lt; 1) <\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>throw new ArgumentOutOfRangeException(&#8220;concurrencyLevel&#8221;);<\/p>\n<p><\/font><\/p>\n<p class=\"Code\">\n<p><font face=\"Consolas\">&nbsp;<\/font><\/p>\n<\/p>\n<p class=\"Code\"><font face=\"Consolas\"><span>&nbsp;&nbsp;&nbsp; <\/span>_tasks = new BlockingCollection&lt;Task&gt;();<\/p>\n<p><\/font><\/p>\n<p class=\"Code\">\n<p><font face=\"Consolas\">&nbsp;<\/font><\/p>\n<\/p>\n<p class=\"Code\"><font face=\"Consolas\"><span>&nbsp;&nbsp;&nbsp; <\/span>_threads = Enumerable.Range(0, numberOfThreads).Select(i =&gt;<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>{<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>var thread = new Thread(() =&gt;<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>{<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>foreach (var t in <\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _tasks.GetConsumingEnumerable())<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>{<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>TryExecuteTask(t);<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>}<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>});<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>thread.IsBackground = true;<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>thread.SetApartmentState(ApartmentState.STA);<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>return thread;<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>}).ToList();<\/p>\n<p><\/font><\/p>\n<p class=\"Code\">\n<p><font face=\"Consolas\">&nbsp;<\/font><\/p>\n<\/p>\n<p class=\"Code\"><font face=\"Consolas\"><span>&nbsp;&nbsp;&nbsp; <\/span>_threads.ForEach(t =&gt; t.Start());<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">}<\/p>\n<p><\/font><\/p>\n<p class=\"Code\">\n<p><font face=\"Consolas\">&nbsp;<\/font><\/p>\n<\/p>\n<p class=\"MsoNormal\"><font size=\"3\"><font face=\"Calibri\">That&rsquo;s the most complicated part of the implementation.<span>&nbsp; <\/span>The QueueTask method on StaTaskScheduler just takes the Task provided by the TPL infrastructure and stores it into the BlockingCollection&lt;Task&gt; (this will be called, for example, during Task.Factory.StartNew):<\/font><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">protected override void QueueTask(Task task)<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">{<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\"><span>&nbsp;&nbsp;&nbsp; <\/span>_tasks.Add(task);<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">}<\/p>\n<p><\/font><\/p>\n<p class=\"Code\">\n<p><font face=\"Consolas\">&nbsp;<\/font><\/p>\n<\/p>\n<p class=\"MsoNormal\"><font size=\"3\"><font face=\"Calibri\">The GetScheduledTasks method, which is used by the debugging infrastructure in Visual Studio to feed the Parallel Tasks and Parallel Stacks window, returns a copy of the contents of the BlockingCollection&lt;Task&gt;:<\/p>\n<p><\/font><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">protected override IEnumerable&lt;Task&gt; GetScheduledTasks()<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">{<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\"><span>&nbsp;&nbsp;&nbsp; <\/span>return _tasks.ToArray();<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">}<\/p>\n<p><\/font><\/p>\n<p class=\"Code\">\n<p><font face=\"Consolas\">&nbsp;<\/font><\/p>\n<\/p>\n<p class=\"MsoNormal\"><font size=\"3\"><font face=\"Calibri\">TryExecuteTaskInline is implemented to allow task inlining (running the Task on the current thread, which might happen in a call to Task.Wait, Task.WaitAll, Task&lt;TResult&gt;.Result, or Task.RunSynchronously), but only if the current thread is an STA thread:<\/p>\n<p><\/font><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">protected override bool TryExecuteTaskInline(<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\"><span>&nbsp;&nbsp;&nbsp; <\/span>Task task, bool taskWasPreviouslyQueued)<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">{<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\"><span>&nbsp;&nbsp;&nbsp; <\/span>return<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>Thread.CurrentThread.GetApartmentState() == <\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ApartmentState.STA &amp;&amp;<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>TryExecuteTask(task);<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">}<\/p>\n<p><\/font><\/p>\n<p class=\"Code\">\n<p><font face=\"Consolas\">&nbsp;<\/font><\/p>\n<\/p>\n<p class=\"MsoNormal\"><font size=\"3\"><font face=\"Calibri\">MaximumConcurrencyLevel returns the number of threads utilized by the scheduler:<\/p>\n<p><\/font><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">public override int MaximumConcurrencyLevel<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">{<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\"><span>&nbsp;&nbsp;&nbsp; <\/span>get { return _threads.Count; }<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">}<\/p>\n<p><\/font><\/p>\n<p class=\"Code\">\n<p><font face=\"Consolas\">&nbsp;<\/font><\/p>\n<\/p>\n<p class=\"MsoNormal\"><font size=\"3\"><font face=\"Calibri\">And finally, our Dispose implementation marks the BlockingCollection&lt;Task&gt; as complete for adding (so that as soon as the collection is empty, the threads will exit), waits for all threads to exit, and then cleans up the BlockingCollection&lt;Task&gt;.<\/p>\n<p><\/font><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">public void Dispose()<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">{<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\"><span>&nbsp;&nbsp;&nbsp; <\/span>if (_tasks != null)<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\"><span>&nbsp;&nbsp;&nbsp; <\/span>{<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>_tasks.CompleteAdding();<\/p>\n<p><\/font><\/p>\n<p class=\"Code\">\n<p><font face=\"Consolas\">&nbsp;<\/font><\/p>\n<\/p>\n<p class=\"Code\"><font face=\"Consolas\"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>foreach (var thread in _threads) thread.Join();<\/p>\n<p><\/font><\/p>\n<p class=\"Code\">\n<p><font face=\"Consolas\">&nbsp;<\/font><\/p>\n<\/p>\n<p class=\"Code\"><font face=\"Consolas\"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>_tasks.Dispose();<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>_tasks = null;<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\"><span>&nbsp;&nbsp;&nbsp; <\/span>}<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">}<\/p>\n<p><\/font><\/p>\n<p class=\"Code\">\n<p><font face=\"Consolas\">&nbsp;<\/font><\/p>\n<\/p>\n<p class=\"MsoNormal\"><font size=\"3\"><font face=\"Calibri\">With this in place, we can now use our StaTaskScheduler.<span>&nbsp; <\/span>Commonly, an StaTaskScheduler will be instantiated with only one thread, so that the same thread is used to process all tasks queued to the scheduler:<\/p>\n<p><\/font><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">var sta = new StaTaskScheduler(numberOfThreads:1);<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\"><\/p>\n<p>&#8230;<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\"><\/p>\n<p>for(int i=0; i&lt;10; i++) <\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\"><\/p>\n<p>{<\/p>\n<p><\/font><\/p>\n<p><font face=\"Consolas\"><\/p>\n<p class=\"Code\"><font face=\"Consolas\"><\/p>\n<p>&nbsp;&nbsp;&nbsp; \/\/ All 10 tasks will be executed sequentially <\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\"><\/p>\n<p>&nbsp;&nbsp;&nbsp; \/\/ due to using only&nbsp;one thread.<\/p>\n<p><\/font><\/p>\n<p class=\"Code\">&nbsp;&nbsp;&nbsp; Task.Factory.StartNew(() =&gt;<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">&nbsp;&nbsp;&nbsp; {<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&#8230; <\/span>\/\/ run code here that requires an STA thread<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">&nbsp;&nbsp;&nbsp; }, CancellationToken.None, TaskCreationOptions.None, sta);<\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">}<\/p>\n<p><\/font><\/p>\n<p class=\"Code\">\n<p><font face=\"Consolas\">&nbsp;<\/font><\/p>\n<\/p>\n<p class=\"MsoNormal\"><font size=\"3\"><font face=\"Calibri\">Another approach, depending on the use case, is to use multiple threads, but to dedicate one STA instance to each thread.<span>&nbsp; <\/span>For example, let&rsquo;s say you have an STA object CoolObject that provides a method DoStuff.<span>&nbsp; <\/span>You want to be able to have multiple tasks take advantage of DoStuff concurrently, but that can&rsquo;t be done on the same object instance, and unfortunately CoolObject is relatively expensive to instantiate.<span>&nbsp; <\/span>A solution then is to store one CoolObject instance in thread-local storage for each of the scheduler&rsquo;s threads, and each task can then access that thread-local copy:<\/p>\n<p><\/font><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">var sta = new StaTaskScheduler(numberOfThreads:4);<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">var coolObject = new ThreadLocal&lt;CoolObject&gt;(() =&gt; new CoolObject());<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">&hellip;<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">for(int i=0; i&lt;N; i++)<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">{<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\"><span>&nbsp;&nbsp;&nbsp; <\/span>Task.Factory.StartNew(() =&gt;<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\"><span>&nbsp;&nbsp; <\/span><span>&nbsp;<\/span>{<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>&#8230;<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>coolObject.Value.DoStuff();<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>&#8230;<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\"><span>&nbsp;&nbsp;&nbsp; <\/span>}, CancellationToken.None, TaskCreationOptions.None, sta);<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">}<\/p>\n<p><\/font><\/p>\n<p class=\"Code\">\n<p><font face=\"Consolas\">&nbsp;<\/font><\/p>\n<\/p>\n<p><span>In general, it&rsquo;s desirable to avoid STA objects, but with legacy code that&rsquo;s a luxury we often can&rsquo;t afford, and it&rsquo;s great to be able to still use TPL with such objects.<\/span><\/p>\n","protected":false},"excerpt":{"rendered":"<p>(The full set of ParallelExtensionsExtras Tour posts is available&nbsp;here.)&nbsp;&nbsp; The Task Parallel Library (TPL) supports a wide array of semantics for scheduling tasks, even though it only includes two in the box (one using the ThreadPool, and one using SynchronizationContext, which exists primarily to run tasks on UI threads).&nbsp; This support comes from the extensibility [&hellip;]<\/p>\n","protected":false},"author":360,"featured_media":58792,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[7908],"tags":[7907,7911,7909,7924,7912],"class_list":["post-55929","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-pfxteam","tag-net-4","tag-code-samples","tag-parallel-extensions","tag-parallelextensionsextras","tag-task-parallel-library"],"acf":[],"blog_post_summary":"<p>(The full set of ParallelExtensionsExtras Tour posts is available&nbsp;here.)&nbsp;&nbsp; The Task Parallel Library (TPL) supports a wide array of semantics for scheduling tasks, even though it only includes two in the box (one using the ThreadPool, and one using SynchronizationContext, which exists primarily to run tasks on UI threads).&nbsp; This support comes from the extensibility [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/55929","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/users\/360"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/comments?post=55929"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/55929\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media\/58792"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media?parent=55929"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/categories?post=55929"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/tags?post=55929"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}