Не каждый результат 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>
,