【问题标题】:Not every result of IProgress<int> comes out of the task并非 IProgress<int> 的每个结果都来自任务
【发布时间】:2015-10-03 09:40:05
【问题描述】:

考虑以下实现,一个方法接受IProgress&lt;int&gt;,迭代超过 10000 个对象。 numbers 数组变量返回 10000 个对象,但 IProgress&lt;int&gt; 只报告 9970 - 9980 个对象。每次运行都会有所不同,因此有些人会“迷路”。

    protected async override Task<int[]> CollectDataAsyncImpl(IProgress<int> progress) {                
        return await Task.Run<int[]>(() => {

            var numbers = new List<int>();

            foreach (var idx in new Int32Range(1, 10000).AsEnumerable().Index()) {                                            

                numbers.Add(idx.Value);                    

                if (progress != null) {
                    progress.Report(idx.Value);
                }

            }

            return numbers.ToArray();
        });
    }

作为参考,这是我运行的测试。它在第三个断言 Assert.Equal(10000, result[9999]); 处失败。

[Fact]
async void ReportsProgress() {            
    var sut = new IntegerCollector();
    var result = new List<int>();
    var output = await sut.CollectDataAsync(new Progress<int>(i => result.Add(i)));
    Assert.Equal(10000, output.Length);
    Assert.Equal(1, result[0]);
    Assert.Equal(10000, result[9999]);
}

显然我做错了什么,或者我不了解任务/线程的内部结构。我对IProgress&lt;int&gt;new Progress&lt;int&gt;(i =&gt; result.Add(i)) 的实现不正确吗?我应该让那个线程安全吗?如果是,我该怎么做?

GitHub 有您可以克隆和测试的代码(如果需要):https://github.com/KodeFoxx/Kf.DataCollection/tree/master/Source/Kf.DataCollection

【问题讨论】:

  • 这是一个标准的线程竞争错误,IProgress.Report() 使用 SynchronizationContext.Post() 方法来调用委托。不像 Send() 那样同步,它稍后完成。仅将 IProgress 用于向用户界面报告对时间要求不严格的进度。

标签: c# multithreading task iprogress


【解决方案1】:

这可能是因为Progress&lt;T&gt; 的实现方式。创建时,Progress&lt;T&gt; 捕获同步上下文并使用它来执行i =&gt; result.Add(i)。由于您正在运行测试,我假设没有同步上下文。在这种情况下,Progress&lt;T&gt; 使用默认的 SynchronizationContext,它将工作项发布到线程池 (ThreadPool.QueueUserWorkItem)。您的任务在线程池处理所有排队项目之前完成,它完美地解释了结果不一致。

检查是否是这种情况的简单方法:将IProgress&lt;int&gt; 参数更改为Action&lt;int&gt; 并直接传递i =&gt; result.Add(i) 委托,而不用Progress&lt;T&gt; 包装它。

【讨论】:

  • 谢谢。这是否意味着我必须为我的测试推出自己的 IProgress 实现,或者我应该依赖 Progress 已经过测试并且会像宣传的那样工作,因为它基本上是一个 .Net 元素。并且仅仅依靠它工作,然后只测试我是否从进度函数中获得至少一个回调?
  • 我将整个测试代码封装在 Task.Run(() =&gt; { ... }); 中,然后它就可以工作了。我认为这是因为它现在有一个上下文。请参阅 pasteboard.co/16jbGC5X.png 的通过测试代码。我认为这是提供上下文的正确方法,还是矫枉过正?
  • 1.是的,您可以为测试提供一个简单的IProgress&lt;T&gt; 实现。 .NET Progress&lt;T&gt; 实现按预期工作,但请注意,它的目的是向 UI 报告进度(这就是使用所有 SynchronizationContext 内容的原因)。如果您想以异步方式使用实际结果,请考虑使用 IObservable&lt;T&gt;IObserver&lt;T&gt;
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-03-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-14
  • 2019-10-07
相关资源
最近更新 更多