Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

System.Progress wrong invocation order

Tags:

c#

.net

delegates

I'm trying to display download progress using HttpClient. For that I use System.Progress<T>. Code looks like this:

long totalRead = 0L;
var buffer = new byte[1024];
bool isMoreToRead = true;
ulong total = (ulong)response.Content.Headers.ContentLength;

while (isMoreToRead)
{
    int read = await stream.ReadAsync(buffer, 0, buffer.Length);
    if (read == 0)
        isMoreToRead = false;
    else
    {
        var data = new byte[read];
        buffer.ToList().CopyTo(0, data, 0, read);
        totalRead += read;
        progress.Report((int) (totalRead*1d / total * 1d * 100) );
    }
}

Assume subscribing looks like this:

var progress = new Progress<int>();
progress.ProgressChanged += (sender, i) => Console.WriteLine(i);
client.Download(file, progress).Wait();

But as a result, progress order is inconsistent, like this: 10, 20, 30, 70, 15, 90, 100, 80.

Is this a default delegate's behaviour, or there's another reason?

like image 475
Toddams Avatar asked Mar 15 '23 15:03

Toddams


1 Answers

Progress<T>.Report is asynchronous; by that I mean, Report method will not raise the ProgressChanged event synchronously before it returns.

Progress<T> class captures the SynchronizationContext and posts the invocation of ProgressChanged event to that captured context.

When used in single threaded context like Winforms, Wpf, your code will work as expected.

In a console app, there will not be any SynchronizationContext and thus Progress<T> posts the invocation to the default SynchronizationContext which delegates to ThreadPool. ThreadPool doesn't guarantee any order, that's the reason you don't see synchronous result.

I believe you're testing this code in Console app or something similar otherwise nothing wrong with your code.

If you need a synchronous version of IProgress<T>, you have to roll your own.

like image 92
Sriram Sakthivel Avatar answered Mar 28 '23 23:03

Sriram Sakthivel