如何在异步函数中从循环报告进度中消除竞争条件?

时间:2014-01-05 06:06:53

标签: c# asynchronous async-await

我正在尝试根据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);
                    }
                }
            });
        }
    }
}

但是,当i不断更新时,在打印之前,输出会混乱。示例输出:

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

我认为IProgress接口旨在通过等待更新进度时要执行的任何lambda表达式或方法来消除此类竞争条件。我错过了任何可以消除这种竞争条件的东西吗?

3 个答案:

答案 0 :(得分:7)

这是因为回调正在一个线程上运行。

为什么?

控制台应用程序。

根据Progress类MSDN文档:

  

提供给使用ProgressChanged事件注册的构造函数或事件处理程序的任何处理程序都是通过构造实例时捕获的SynchronizationContext实例调用的。如果在构造时没有当前的SynchronizationContext,则将在ThreadPool上调用回调。

基本上,你的回调是不合适的,因为Console应用程序中没有SynchronizationContext ..所以它们在ThreadPool线程上被触发。

在Windows应用程序中试用..这是完全相同代码的输出截图:

Sync example

答案 1 :(得分:3)

首先 - 没有“as i不断更新,然后才能打印...”代码:

  • for循环和委托中的变量完全不相关。
  • 没有传递给.Report的lambda可能已经抓取i - Report(T value)在您的情况下需要int,并且无法报告不同的值。

您可以通过将变量名称更改为不同来说服自己。

第二:报告很好,但是随机收到的通知(由Simon Whitehead回答)

通过向任务添加控制台跟踪,您可以轻松查看报告的值是否正确:

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

附注:通常你会用async编写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);
            }
        }
    });
}

答案 2 :(得分:0)

所以这引出了一个问题:如何为控制台应用程序初始化SynchronizationContext?我正在使用这个标题为的答案:

Await,SynchronizationContext和Console Apps

  

异步方法执行的其余部分所使用的延续将是   发布到SynchronizationContext.Current,除了它是一个控制台   应用程序,它是null(除非您明确覆盖它   SynchronizationContext.SetSynchronizationContext)。

本文接着向您展示如何在控制台应用程序中编写和设置synchronizationContext

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

从你的头脑里进入你的心灵。