不是每一个IProgress<int>的结果都是从任务中出来的

Not every result of IProgress<int> comes out of the task

考虑以下实现,一个方法接受一个 IProgress<int>,迭代 10000 个对象。 numbers 数组变量 returns 10000 个对象,但 IProgress<int> 仅报告 9970 - 9980 个对象。它因 运行 而异,所以有些人得到 "lost"。

    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]);
}

显然我做错了什么,或者我不了解task/threading的内部结构。我对 IProgress<int>new Progress<int>(i => result.Add(i)) 的实现不正确吗?我应该使该线程安全吗?如果是,我该怎么做?

GitHub 具有您可以在需要时克隆和测试的代码:https://github.com/KodeFoxx/Kf.DataCollection/tree/master/Source/Kf.DataCollection

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

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