Rodolphe
Rodolphe

Reputation: 1731

Best way to delay execution

Let's say I have a method that I run in a separate thread via Task.Factory.StartNew().
This method reports so many progress (IProgress) that it freezes my GUI. I know that simply reducing the number of reports would be a solution, like reporting only 1 out of 10 but in my case, I really want to get all reports and display them in my GUI.

My first idea was to queue all reports and treat them one by one, pausing a little bit between each of them.

Firstly: Is it a good option?

Secondly: How to implement that? Using a timer or using some kind of Task.Delay()?

UPDATE:
I'll try to explain better. The progress sent to the GUI consists of geocoordinates that I display on a map. Displaying each progress one after another provide some kind of animation on the map. That's why I don't want to skip any of them.

In fact, I don't mind if the method that I execute in another thread finishes way before the animation. All I want, is to be sure that all points have been displayed for at least a certain amount of time (let's say 200 ms).

Upvotes: 1

Views: 163

Answers (2)

VMAtm
VMAtm

Reputation: 28355

I have many concerns regarding your solution, but I can't say for sure which one can be a problem without code samples.

First of all, Stephen Cleary in his StartNew is Dangerous article points out the real problem with this method with using it with default parameters:

Easy enough for the simple case, but let’s consider a more realistic example:

private void Form1_Load(object sender, EventArgs e)
{
    Compute(3);
}

private void Compute(int counter)
{
    // If we're done computing, just return.
    if (counter == 0)
        return;

    var ui = TaskScheduler.FromCurrentSynchronizationContext();
    Task.Factory.StartNew(() => A(counter))
        .ContinueWith(t =>
        {
            Text = t.Result.ToString(); // Update UI with results.

            // Continue working.
            Compute(counter - 1);
        }, ui);
}

private int A(int value)
{
    return value; // CPU-intensive work.
}

... Now, the question returns: what thread does A run on? Go ahead and walk through it; you should have enough knowledge at this point to figure out the answer.
Ready? The method A runs on a thread pool thread the first time, and then it runs on the UI thread the last two times.

I strongly recommend you to read whole article for better understanding the StartNew method usage, but want to point out the last advice from there:

Unfortunately, the only overloads for StartNew that take a TaskScheduler also require you to specify the CancellationToken and TaskCreationOptions. This means that in order to use Task.Factory.StartNew to reliably, predictably queue work to the thread pool, you have to use an overload like this:

Task.Factory.StartNew(A, CancellationToken.None,
  TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);

And really, that’s kind of ridiculous. Just use Task.Run(() => A());.

So, may be your code can be easily improved simply by switching the method you are creating news tasks. But there is some other suggestions regarding your question:

  1. Use BlockingCollection for the storing the reports, and write a simple consumer from this queue to UI, so you'll always have a limited number of reports to represent, but at the end all of them will be handled.
  2. Use a ConcurrentExclusiveSchedulerPair class for your logic: for generating the reports use the ConcurrentScheduler Property and for displaying them use ExclusiveScheduler Property.

Upvotes: 1

Kjartan
Kjartan

Reputation: 19081

Sounds like the whole point of having the process run in a separate thread is wasted if this is the result. As such, my first recommendation would be to reduce the number of updates if possible.

If that is out of the question, perhaps you could revise the data you are sending as part of each update. How large, and how complex is the object or data-structure used for reporting? Can performance be improved by reducing it's complexity?

Finally, you might try another approach: What if you create a third thread that just handles the reporting, and delivers it to your GUI in larger chunks? If you let your worker-thread report it's status to this reporter-thread, then let the reporter thread report back to your main GUI-thread only occasionally (e.g. every 1 in 10, as you suggest yourself above, bur then reporting 10 chunks of data at once), then you won't call on your GUI that often, yet you'll still be able to keep all the status data from the processing, and make it available in the GUI.

I don't know how viable this will be for your particular situation, but it might be worth an experiment or two?

Upvotes: 2

Related Questions