Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Console messages appearing in incorrect order when reporting progress with IProgress.Report()

I have noticed a following behaviour. Console output messages appear in an incorrect folder when they are populated by IProgress.

var recounter = new IdRecounter(filePath, new Progress<string>(Console.WriteLine));
                recounter.RecalculateIds();

I am trying to improve my encapsulation, reusability and design skills. So, I have a class called IdRecounter. I want to use it in a console app for now, but later on perhaps in a WPF app or whatever.

Therefore, I want the class to be completely unaware of it's environment - but at the same time I want to report progress of action 'live' - therefore, I am using the IProgress type, which will allow me to put stuff into console, or into a log file, or update a status label property etc. (Please let me know if that's not how you do it)

So, I have noticed that it tends to throw messages into console in an incorrect order, e.g. Processing File 1
Processing File 4
Processing File 5
Processing File 3
All done!
Processing File 2

When I switched IProgress (MyProgress.Report()) with Console.WriteLine() it works as expected.
What is the reason of that and how can it be controlled?

Thanks

like image 776
Bartosz Avatar asked Nov 13 '15 18:11

Bartosz


2 Answers

The Progress<T> class uses the current synchronization context for the thread in which it was created to invoke the event handlers for its ProgressChanged event.

In a console application, the default synchronization context uses the thread pool to invoke delegates, rather than marshaling them back to the thread where the context is retrieved. This means that each time you update progress, the event handler may be invoked in a different thread (especially if the progress updates occur in quick succession).

Due to the way threads are scheduled, there is no guarantee that a thread pool worker assigned a task before another thread pool worker will actually run its task before that other worker runs its task. Especially for relatively simple tasks (such as emitting progress messages), it can easily be the case then that tasks enqueued later are actually completed before tasks enqueued earlier.

If you want for your progress update messages to be guaranteed to be displayed in order, you'll need to use a different mechanism. For example, you could set up a producer/consumer with BlockingCollection<T>, where you have a single thread consuming messages that are queued (produced) by your operations that report progress. Or, of course, you could just call Console.WriteLine() directly (as you have already verified will work).

Note that that doesn't mean you need to abandon the idea of using IProgress<T>. It just means you would need to provide your own implementation, rather than using the Progress<T> class, at least in the console scenario. For example:

class ConsoleProgress : IProgress<string>
{
    public void ReportProgress(string text)
    {
        Console.WriteLine(text);
    }
}

This would allow you to, for example, keep the IProgress<T> abstraction in the IdRecounter() class, decoupling that type from the UI context. It could be reused for a console program as well as any GUI API program, such as Winforms, WPF, Winrt, etc.


The bottom line: Progress<T> is a very useful implementation of IProgress<T> when you need to abstract the cross-thread, synchronization context-related operations that are needed in a GUI program. It will work in console programs, but because it will use the thread pool in that case, you may not get deterministically ordered output, at least not without including additional synchronization to the ProgressChanged event handlers.

like image 97
Peter Duniho Avatar answered Nov 14 '22 20:11

Peter Duniho


This class mimics the basic behavior of Progress<T> for a console application:

public class ConsoleProgress<T> : IProgress<T>
{
    private Action<T> action;

    public ConsoleProgress(Action<T> action)
    {
        this.action = action;
    }
    public void Report(T value)
    {
        action(value);
    }
}

There is action only, but the event implementation just like in Progress<T> would be simple too.

like image 33
Tomas Avatar answered Nov 14 '22 21:11

Tomas