Here is some easy piece of code to show the unexpected behavior:
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        _UI = TaskScheduler.FromCurrentSynchronizationContext();
        Loaded += new RoutedEventHandler(MainWindow_Loaded);
    }
    TaskScheduler _UI;
    void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
        Task.Factory.StartNew(() =>
        {
            //Expected: Worker thread
            //Found: Worker thread
            DoSomething();
        })
        .ContinueWith(t =>
            {
                //Expected: Main thread
                //Found: Main thread
                DoSomething();
                Task.Factory.StartNew(() =>
                {
                    //Expected: Worker thread
                    //Found: Main thread!!!
                    DoSomething();
                });
            }, _UI);
    }
    void DoSomething()
    {
        Debug.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString());
    }
}
Why is the inner task executed in the main thread? How can i prevent this behavior?
Unfortunately, the Current task scheduler, when you're running your continuation, becomes the SynchronizationContextTaskScheduler setup by your TaskScheduler.FromCurrentSynchronizationContext.  
This is discussed in this Connect Bug - and was written this way by design in .NET 4. However, I agree that the behavior leaves a bit to be desired here.
You can work around this by grabbing a "background" scheduler in your constructor, and using it:
TaskScheduler _UI;
// Store the default scheduler up front
TaskScheduler _backgroundScheduler = TaskScheduler.Default; 
public MainWindow()
{
    InitializeComponent();
    _UI = TaskScheduler.FromCurrentSynchronizationContext();
    Loaded += new RoutedEventHandler(MainWindow_Loaded);
}
Once you have that, you can easily schedule your "background" task appropriately:
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
    Task.Factory.StartNew(() =>
    {
        //Expected: Worker thread
        //Found: Worker thread
        DoSomething();
    })
    .ContinueWith(t =>
        {
            //Expected: Main thread
            //Found: Main thread
            DoSomething();
            // Use the _backgroundScheduler here
            Task.Factory.StartNew(() =>
            {
                DoSomething();
            }, CancellationToken.None, TaskCreationOptions.None, _backgroundScheduler);
        }, _UI);
}
Also, in this case, since your operation is at the end, you could just put it in its own continuation and get the behavior you want. This, however, is not a "general purpose" solution, but works in this case:
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
    Task.Factory.StartNew(() =>
    {
        //Expected: Worker thread
        //Found: Worker thread
        DoSomething();
    })
    .ContinueWith(t =>
        {
            //Expected: Main thread
            //Found: Main thread
            DoSomething();
        }, _UI)
    .ContinueWith(t =>
            {
                //Expected: Worker thread
                //Found: Is now worker thread
                DoSomething();
            });
}
Apart from @reed-copsey`s great answer I want to add that if you want to force your task to be executed on a threadpool thread you can also use the TaskScheduler.Default property which always refers to the ThreadPoolTaskScheduler:
return Task.Factory.StartNew(() =>
{
   Console.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString());
}, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default);
This way you dont have to capture the task scheduler in a variable like proposed in @reed-copsey `s answer.
More information on TaskSchedulers can be found here: TaskSchedulers on MSDN
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With