-2

Playing with Progress class:

static async Task MyMethodAsync(IProgress<double> progress = null)
{
    int done = 0;
    while (done<100)
    {
        if (progress != null)
            progress.Report(done);
        done++;
    }
}

static async Task CallMyMethodAsync()
{
    var progress = new Progress<double>();
    progress.ProgressChanged += (sender, args) =>
        {
            Console.WriteLine("progr " + args);
        };
    await MyMethodAsync(progress);
}

public static void Main()
{
    CallMyMethodAsync();
    Console.WriteLine("done with caller");
    Console.ReadLine();
}

Got output not in correct order:

done with caller
progr 2
progr 3
progr 4
progr 5
progr 6
progr 7
progr 8
progr 9
progr 10
progr 12
progr 11
progr 0
progr 13
progr 16
progr 17
progr 18
progr 19
progr 20
progr 21
progr 22
progr 23
progr 24
progr 25
progr 26

Why and how to achieve correct order?

Patrick Hofman
  • 153,850
  • 22
  • 249
  • 325
vico
  • 17,051
  • 45
  • 159
  • 315
  • 3
    I'm guessing this is a console application? Those don't have Synchronization Contexts and so [`Progress` callbacks are serviced on the Thread Pool](https://msdn.microsoft.com/en-us/library/hh193692(v=vs.110).aspx#Remarks). So you don't know when they'll actually be scheduled and multiple callbacks may be in flight at the same time and then be competing for the `Console` lock to actually perform their writes. – Damien_The_Unbeliever Nov 08 '17 at 10:42
  • Maybe IProgress is not suitable to track progress on very short time frame. If you just put a tempo inside your loop everything should work properly. In a normal use case, IProgress should be used to track progress on function call and not loop iteration (which are a lot more faster) – Ko2r Nov 08 '17 at 10:43

4 Answers4

0

The Progress<T> class is asynchronous. It reports the progress in a different thread, since it is a console app, as Damien_The_Unbeliever explains:

Those don't have Synchronization Contexts and so Progress callbacks are serviced on the Thread Pool. So you don't know when they'll actually be scheduled and multiple callbacks may be in flight at the same time and then be competing for the Console lock to actually perform their writes

You can make your own implementation of IProgress<T>, which is synchronous. Use this one:

public class MyProgress<T> : IProgress<T>
{
    public event ProgressChangedEventHandler<T> ProgressChanged;

    public void Report(T value)
    {
        ProgressChanged?.Invoke(this, value);
    }
}

public delegate void ProgressChangedEventHandler<T>(object sender, T progress);

You just have to change your calling code to:

var progress = new MyProgress<double>();
Patrick Hofman
  • 153,850
  • 22
  • 249
  • 325
0

What you mean with "block and done with...".

The Progress class is asynchronous. It reports the progress in a different thread, since it is a console app, as Damien_The_Unbeliever explains:

This post should explain why you can "lose" some calls.

I have feeling i'm far from details of your application but the simple question is do you really need to provide multithreading solution for this?

If you really need execute process in other thread and update UI you can use next logic:

class Class1
{
    public delegate void ProcessProgressEventHandler(int progress);
    public ProcessProgressEventHandler ProcessProgress;
    public EventHandler ProcessEnd;
    public void StartHeavyProcess()
    {
        System.Threading.Thread vThread = new System.Threading.Thread(HeavyProcess);
        ProcessProgress += OnProgress;
        ProcessEnd += OnProcessEnd;
        vThread.Start();
    }

    private void OnProcessEnd(object sender, EventArgs e)
    {
        Console.WriteLine("Process end!");
    }

    private void OnProgress(int progress)
    {
        Console.WriteLine("Progress: " + progress.ToString());
    }

    public void HeavyProcess()
    {
        int done = 0;
        while (done < 100)
        {
            if (ProcessProgress != null)
                ProcessProgress(done);
            done++;
        }
        if (ProcessEnd != null)
            ProcessEnd(this, new EventArgs());
    }
}

P.S. Pay attention if your handler call windows control moethods you must Invoke method in UI thread!

-1

The result you get because you use Task which start new thread.

public static void Main()
{
    CallMyMethodAsync();
    Console.WriteLine("done with caller");
    Console.ReadLine();
}

For example first command 'CallMyMethodAsync();' just start new process and next command 'Console.WriteLine("done with caller");' is executed before end of process started on first step. If you want to execute command from line 2 after end of process started on line 1 it is:

public static void Main()
{
    CallMyMethodAsync()**.Wait();**
    Console.WriteLine("done with caller");
    Console.ReadLine();
}
Patrick Hofman
  • 153,850
  • 22
  • 249
  • 325
  • 1
    That's true for the "done with caller" line, however you can see that the progress does not increment properly either – Igor Meszaros Nov 08 '17 at 11:02
  • It is strange, but calling `CallMyMethodAsync().Wait()` still not guarantee "done with caller" will be last line. Why? – vico Nov 08 '17 at 12:33
  • @vico I explained you in my answer. – Patrick Hofman Nov 08 '17 at 13:08
  • You explained `If you want to execute command from line 2 after end of process started on line 1 it is:` - use wait(). I expect end of process 1 is printed all process lines. But I got `done with caller` before all progress messages are printed – vico Nov 08 '17 at 14:02
  • Console.WriteLine(...) commands are executed in thread number 3 (other than thread 1(waithing for end of 2) and thread 2(in loop)). this make impossible to guarantee that loop thread will execute next command when thread 3 finish!!! – Ivan Alexandrov Nov 08 '17 at 19:56
-1

Ok, Reason for that is you use "events" but "Event dispatcher" does not guarantee that events will be processed chronologically.

The simpliest solution I know is to use delegate to inform for progress:

static async Task MyMethodAsync(Action<double> progress = null)
{
    int done = 0;
    while (done<100)
    {
        if (progress != null)
            progress.Invoke(done);
        done++;
    }
}
  • In case I use delegate function `CallMyMethodAsync()` blocks and `done with caller` is printed at the very bottom. Why? – vico Nov 08 '17 at 13:56