问题 使用TPL的Parallel.ForEach时跟踪进度


跟踪以下进度的最佳方式是什么?

long total = Products.LongCount();
long current = 0;
double Progress = 0.0;

Parallel.ForEach(Products, product =>
{
    try
    {
        var price = GetPrice(SystemAccount, product);
        SavePrice(product,price);
    }
    finally
    {
        Interlocked.Decrement(ref this.current);
    }});

我想将进度变量从0.0更新为1.0(当前/总)但我不想使用会对并行性产生负面影响的任何内容。


3915
2018-01-26 11:51


起源

正在使用的进展是什么?更新UI?如果是这样,您是否可以根据当前和总计的值让UI更新(使用计时器)?在UI上每秒执行太多更新可能会阻止它。最好让UI设置更新的速度而不是处理器。 - Cameron MacFarland


答案:


Jon的解决方案很好,如果您需要这样的简单同步,您的第一次尝试应该几乎总是使用 lock。但是,如果你测量锁定减慢了太多的东西,你应该考虑使用类似的东西 Interlocked

在这种情况下,我会用 Interlocked.Increment 增加当前计数,并改变 Progress 进入一个属性:

private long total;
private long current;
public double Progress
{
    get
    {
        if (total == 0)
            return 0;
        return (double)current / total;
    }
}

…

this.total = Products.LongCount();
this.current = 0;

Parallel.ForEach(Products, product =>
{
    try
    {
        var price = GetPrice(SystemAccount, product);
        SavePrice(product, price);
    }
    finally
    {
        Interlocked.Increment(ref this.current);
    }
});

此外,您可能想要考虑如何处理异常,我不确定以异常结束的迭代应该算作完成。


7
2018-01-26 13:01





由于您只是进行了一些快速计算,因此通过锁定适当的对象来确保原子性:

long total = Products.LongCount();
long current = 0;
double Progress = 0.0;
var lockTarget = new object();

Parallel.ForEach(Products, product =>
{
    try
    {
        var price = GetPrice(SystemAccount, product);
        SavePrice(product,price);
    }
    finally
    {
        lock (lockTarget) {
            Progress = ++this.current / total;
        }
    }});

3
2018-01-26 12:05





一种不使用身体任何阻塞的解决方案:

long total = Products.LongCount();
BlockingCollection<MyState> states = new BlockingCollection<MyState>();

Parallel.ForEach(Products, () =>
{
    MyState myState = new MyState();
    states.Add(myState);
    return myState;
},
(i, state, arg3, myState) =>
{
    try
    {
        var price = GetPrice(SystemAccount, product);
        SavePrice(product,price);
    }
    finally
    {
        myState.value++;
        return myState;
    }
},
i => { }
);

然后,要访问当前进度:

(float)states.Sum(state => state.value) / total

1
2018-01-26 12:13



对于像这样简单的事情来说,这似乎太复杂了。 - svick