简体   繁体   中英

How to eliminate race condition in reporting progress from loop in an asynchronous function?

I am trying to report progress from an asynchronous function according to the Task-based Asynchronous Pattern .

using System;
using System.Threading.Tasks;

namespace TestConsoleApplication1
{
    class Program
    {
        static void Main()
        {
            Blah();
            Console.ReadLine();
        }

        static async void Blah()
        {
            var ProgressReporter = new Progress<int>(i => Console.WriteLine(i + "%"));
            await Foo(ProgressReporter);
        }

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

However, as i keeps getting updated, before it can be printed, the output is messed up. Example Output:

1%
3%
0%
4%
5%
6%
8%
7%
9%
2%

My belief was that the IProgress interface aims to eliminate such race conditions by awaiting any lambda expressions or methods to be executed when the progress is updated. Am I missing anything which would eliminate this race condition?

It is because the callback is being run on a thread.

Why?

The Console application.

According to the Progress class MSDN documentation:

Any handler provided to the constructor or event handlers registered with the ProgressChanged event are invoked through a SynchronizationContext instance captured when the instance is constructed. If there is no current SynchronizationContext at the time of construction, the callbacks will be invoked on the ThreadPool.

Essentially, your callbacks are out of whack because there is no SynchronizationContext in a Console application.. so they are being fired on ThreadPool threads.

Try it in a Windows application.. here's a screenshot of the output for the exact same code:

同步范例

First - there is not "as i keeps getting updated, before it can be printed..." code:

  • Variables inside your for loop and delegate are completely unrelated.
  • There is no lambda passed to .Report that could have captured i - Report(T value) takes int in your case and have no way to report different value.

You can convince yourself that by changing variable names to be different.

Second: reporting is fine, but notifications received in random order (explained by Simon Whitehead answer)

You can easily see that values reported are correct by adding console trace to the task:

      onProgressPercentChanged.Report(i / 100);
      Console.WriteLine("--" + i + "%");

Side note: normally you'd write async function with async :

static async Task Foo(IProgress<int> onProgressPercentChanged)
{
    await Task.Run(() =>
    {
        for (int i = 0; i < 1000; i++)
        {
            if (i % 100 == 0)
            {
                onProgressPercentChanged.Report(i / 100);
            }
        }
    });
}

So this begs the question: How to initialize a SynchronizationContext for a console application? I am using this answer titled:

Await, SynchronizationContext, and Console Apps

The continuations used by the remainder of the async method's execution would have been posted to SynchronizationContext.Current, except that it a console app, it's null (unless you explicitly override that with SynchronizationContext.SetSynchronizationContext).

The article goes on to show you how to write and setup a synchronizationContext in a console app

http://blogs.msdn.com/b/pfxteam/archive/2012/01/20/10259049.aspx

Peace out of your head and into your heart.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM