flolim
flolim

Reputation: 327

Recursive Task Queue

I'm trying to iterate through a queue - taking 1 item from the queue, processing it in a background task, updating the UI, and then taking the next item, and so on. The problem is the first item is processed in a background task (thread) but then the subsequent items are processed in the UI thread - blocking the UI.

Does anyone know why this happens and how to get around this problem? My full test code is below. Note: this code is for my learning and future reference - not any real application.

public partial class MainWindow : Window
{
    private Queue<int> testQueue = new Queue<int>();
    private TaskScheduler uiScheduler;

    public MainWindow()
    {
        InitializeComponent();

        this.uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
        this.testQueue = new Queue<int>();
        this.testQueue.Enqueue(3);
        this.testQueue.Enqueue(6);
        this.testQueue.Enqueue(7);
        this.testQueue.Enqueue(11);
        this.testQueue.Enqueue(13);
    }

    // just a method that takes about 1 second to run on a modern pc
    private double SumRootN(int root)
    {
        double result = 0;
        for (int i = 1; i < 10000000; i++)
        {
            result += Math.Exp(Math.Log(i) / root);
        }
        return result;
    }

    private void testQueueButton_Click(object sender, RoutedEventArgs e)
    {
        this.processQueue();
    }

    private void processQueue()
    {
        if (this.testQueue.Count > 0)
        {
            int root = this.testQueue.Dequeue();
            Task<double>.Factory.StartNew(() => SumRootN(root))
                .ContinueWith(t =>
                {
                    this.statusText.Text += String.Format("root {0} : {1}\n", root, t.Result);
                    this.processQueue();
                }, uiScheduler);
        }
        else
        {
            this.statusText.Text += "Done\n";
        }
    }
}

Upvotes: 3

Views: 3513

Answers (2)

Nikola Bogdanović
Nikola Bogdanović

Reputation: 3213

That's because you are using TaskScheduler.FromCurrentSynchronizationContext() - you do know what it does right? (makes it run on the same thread it is called, in your case the UI)

EDIT: usr answered you why is that happening, but you could also do this (for a quasi parallel processing):

    int root = this.testQueue.Dequeue();
    Task<double>.Factory.StartNew(() => SumRootN(root))
        .ContinueWith(t =>
        {
            this.statusText.Text += String.Format("root {0} : {1}\n", root, t.Result);
        }, uiScheduler);
    this.processQueue();

Upvotes: 0

usr
usr

Reputation: 171178

Thank you for posting a repro which allowed me to debug.

Task.Factory.StartNew runs your task on the scheduler (factoryScheduler ?? currentTaskScheduler ?? threadPoolScheduler). You got into case 2: Your new task inherits the scheduler from its parent.

I noticed your curious use of recursive calls in order to simulate a loop. If you do it like this, the problem goes away:

         Task<double>.Factory.StartNew(() => SumRootN(root))
            .ContinueWith(t =>
            {
                this.statusText.Text += String.Format("root {0} : {1}\n", root, t.Result);
            }, uiScheduler).ContinueWith(t => { this.processQueue(); });

Upvotes: 3

Related Questions