Note that there are some explanatory texts on larger screens.

plurals
  1. POWhy is TaskScheduler.Current the default TaskScheduler?
    primarykey
    data
    text
    <p>The Task Parallel Library is great and I've used it a lot in the past months. However, there's something really bothering me: the fact that <a href="http://msdn.microsoft.com/en-us/library/system.threading.tasks.taskscheduler.current.aspx"><code>TaskScheduler.Current</code></a> is the default task scheduler, not <a href="http://msdn.microsoft.com/en-us/library/system.threading.tasks.taskscheduler.default.aspx"><code>TaskScheduler.Default</code></a>. This is absolutely not obvious at first glance in the documentation nor samples.</p> <p><code>Current</code> can lead to subtle bugs since its behavior is changing depending on whether you're inside another task. Which can't be determined easily.</p> <p>Suppose I am writting a library of asynchronous methods, using the standard async pattern based on events to signal completion on the original synchronisation context, in the exact same way XxxAsync methods do in the .NET Framework (eg <code>DownloadFileAsync</code>). I decide to use the Task Parallel Library for implementation because it's really easy to implement this behavior with the following code:</p> <pre><code>public class MyLibrary { public event EventHandler SomeOperationCompleted; private void OnSomeOperationCompleted() { var handler = SomeOperationCompleted; if (handler != null) handler(this, EventArgs.Empty); } public void DoSomeOperationAsync() { Task.Factory .StartNew ( () =&gt; Thread.Sleep(1000) // simulate a long operation , CancellationToken.None , TaskCreationOptions.None , TaskScheduler.Default ) .ContinueWith (t =&gt; OnSomeOperationCompleted() , TaskScheduler.FromCurrentSynchronizationContext() ); } } </code></pre> <p>So far, everything works well. Now, let's make a call to this library on a button click in a WPF or WinForms application:</p> <pre><code>private void Button_OnClick(object sender, EventArgs args) { var myLibrary = new MyLibrary(); myLibrary.SomeOperationCompleted += (s, e) =&gt; DoSomethingElse(); myLibrary.DoSomeOperationAsync(); } private void DoSomethingElse() { ... Task.Factory.StartNew(() =&gt; Thread.Sleep(5000)/*simulate a long operation*/); ... } </code></pre> <p>Here, the person writing the library call chose to start a new <code>Task</code> when the operation completes. Nothing unusual. He or she follows examples found everywhere on the web and simply use <code>Task.Factory.StartNew</code> without specifying the <code>TaskScheduler</code> (and there is no easy overload to specify it at the second parameter). The <code>DoSomethingElse</code> method works fine when called alone, but as soon at it's invoked by the event, the UI freezes since <code>TaskFactory.Current</code> will reuse the synchronization context task scheduler from my library continuation.</p> <p>Finding out this could take some time, especially if the second task call is buried down in some complex call stack. Of course, the fix here is simple once you know how everything works: always specify <code>TaskScheduler.Default</code> for any operation you're expecting to be running on the thread pool. However, maybe the second task is started by another external library, not knowing about this behavior and naively using <code>StartNew</code> without a specific scheduler. I'm expecting this case to be quite common.</p> <p>After wrapping my head around it, I can't understand the choice of the team writing the TPL to use <code>TaskScheduler.Current</code> instead of <code>TaskScheduler.Default</code> as the default:</p> <ul> <li>It's not obvious at all, <code>Default</code> is not the default! And the documentation is seriously lacking.</li> <li>The real task scheduler used by <code>Current</code> depends of the call stack! It's hard to maintain invariants with this behavior.</li> <li>It's cumbersome to specify the task scheduler with <code>StartNew</code> since you have to specify the task creation options and cancellation token first, leading to long, less readable lines. This can be alleviated by writing an extension method or creating a <code>TaskFactory</code> that uses <code>Default</code>.</li> <li>Capturing the call stack has additional performance costs.</li> <li>When I really want a task to be dependent on another parent running task, I prefer to specify it explicitly to ease code reading rather than rely on call stack magic.</li> </ul> <p>I know this question may sound quite subjective, but I can't find a good objective argument as to why this behavior is as it. I'm sure I'm missing something here: that's why I'm turning to you.</p>
    singulars
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload