Не каждый результат IProgress<int> выходит из задачи

Рассмотрим следующую реализацию, метод принимает IProgress<int>, перебирает более 10000 объектов. numbers переменная массива возвращает 10000 объектов, но IProgress<int> сообщает только между 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<int> в new Progress<int>(i => result.Add(i)) неправильно? Должен ли я сделать этот поток безопасным, и если да, то как мне это сделать?

GitHub имеет код, который вы можете клонировать и протестировать, если это необходимо: https://github.com/KodeFoxx/Kf.DataCollection/tree/master/Source/Kf.DataCollection

1 ответ

Решение

Это, вероятно, из-за того, как Progress<T> реализовано. Когда создан, Progress<T> захватывает контекст синхронизации и использует его для выполнения i => result.Add(i), Поскольку вы запускаете тест, я предполагаю, что нет контекста синхронизации. В этом случае Progress<T> использует по умолчанию SynchronizationContext, который отправляет рабочие элементы в пул потоков (ThreadPool.QueueUserWorkItem). Ваша задача завершается до того, как пул потоков обрабатывает все элементы в очереди, и это прекрасно объясняет несогласованность результатов.

Простой способ проверить, так ли это: изменить IProgress<int> аргумент Action<int> и передать i => result.Add(i) делегировать напрямую, не оборачивая его Progress<T>,

Другие вопросы по тегам