2
Task Foo(IProgress<int> onProgressPercentChanged){
    return Task.Run(() =>{
        for (int i = 0; i < 1000; i++){
            if (i % 10 == 0)
                onProgressPercentChanged.Report(i / 10);
            //Some operation
        }
    });
}

var progress = new Progress<int>(i => Console.WriteLine(i + " %"));
await Foo(progress);
Thread.Sleep(10000);

The above code prints my progress reports in the incorrect order. I suspected that this issue was related to synchronization, as when I add Thread.Sleep(10) at the //Some operation place, everything starts working correctly. How can I achieve correctness without the unnecessary Thread.Sleep(10)?

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
  • Just don't use Progress<> in a console mode app. It doesn't do anything useful beyond the synchronization that is already built into Console.WriteLine(). – Hans Passant Aug 09 '23 at 22:47

2 Answers2

2

It probably depends on your definition of "correctness".

According to the docs, progress handlers are invoked using the current synchronization context or (if there is none) the ThreadPool.

In theory, you could set up a synchronization context that tries to only have one active thread at a time, but that might defeat the purpose of using a Progress instance. If you want to make sure that each value is handled in order, you could use something more along the lines of an Observable, like Subject from System.Reactive:

async Task Foo(ISubject<int> onProgressPercentChanged)
{
    for (int i = 0; i < 1000; i++)
    {
        if (i % 10 == 0)
            onProgressPercentChanged.OnNext(i / 10);
        //Some operation
    }
    onProgressPercentChanged.OnCompleted();
}


var progress = new Subject<int>();
using (progress.Subscribe(i => Console.WriteLine(i + " %")))
{
    await Foo(progress);
}

Or, if you need the multi-threaded nature of the Progress class, and you're okay with skipping some of the progress indicators, you can adjust your handler to make sure it doesn't go backwards.

object mutex = new object();
int latestProgress = 0;
var progress = new Progress<int>(i =>
    {
        lock (mutex)
        {
            latestProgress = Math.Max(latestProgress, i);
            if (latestProgress == i)
            {
                Console.WriteLine(i + " %");
            }
        }
    });

await Foo(progress);
StriplingWarrior
  • 151,543
  • 27
  • 246
  • 315
1

I think that the Progress<T> class was designed having in mind environments with a special SynchronizationContext installed, like WinForms and WPF applications. In those environments the Action<T> handler is going to be invoked in a synchronized fashion, without overlapping, and with FIFO order preserved. Console applications are not equipped with a SynchronizationContext, so the Action<T> handler is invoked on the ThreadPool, without any protection against concurrent or out-of-order executions. What you can do is to either install a proper SynchronizationContext on your console application, or use a custom implementation of the IProgress<T> interface, that invokes the Action<T> handler synchronously.

The first option requires the AsyncEx NuGet package:

AsyncContext.Run(async () =>
{
    var progress = new Progress<int>(i => Console.WriteLine(i + " %"));
    await Foo(progress);
});

The AsyncContext.Run is a blocking call, similar to the Application.Run method. It installs a special SynchronizationContext on the current thread, much like the Application.Run installs a WindowsFormsSynchronizationContext on the UI thread.

For the second option see this answer (the SynchronousProgress<T> class).

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104