Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Inner task is executed in an unexpected thread

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?

like image 642
Daniel Jant Avatar asked Sep 07 '11 18:09

Daniel Jant


2 Answers

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();
            });
}
like image 135
Reed Copsey Avatar answered Sep 26 '22 22:09

Reed Copsey


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

like image 44
David Roth Avatar answered Sep 24 '22 22:09

David Roth