如何在大型数据集上执行LINQ表达式时报告进度

时间:2009-03-17 20:02:30

标签: linq

如果我需要使用LINQ生成一个相当大的数据集,它可能需要一段时间(比如说几秒钟),我需要(想要)生成反馈用于%'年龄完成,是否有一个简单的/首选方式吗?

示例,假设我列出了包含1000辆汽车的列表,列出了包含1000辆卡车的B,我想选择所有可能的订购(汽车,卡车)对,其中car.color == truck.color链接:

var pairs = from car in A 
            from truck in B 
            where car.color==truck.color 
            select new {car, truck};

现在,在某些时候,这将被评估为一组嵌套的foreach循环。我希望能够报告%'年龄完整,因为它可以进行交互并理想地更新进度条或其他内容。

编辑:在我的查询之后,我将结果存储在一个成员变量中作为这样的列表(强制查询执行):

mPairs = pairs.ToList();

我这样做是因为我在后台工作线程中执行它,因为我不希望UI线程冻结,因为它在UI线程上按需评估LINQ表达式(这是在Silverlight BTW中)。因此,为什么我要报告进展情况。用户体验基本上是这样的:

  1. 用户将项目拖放到工作区
  2. 引擎然后在后台线程上启动,以确定工作区中所有其他项目的(多个)连接可能性。
  3. 当引擎正在计算UI时,不允许新连接和报告进度以指示新项目何时“可连接”到其他项目(所有可能的连接路径都已通过LINQ确定)。
  4. 当引擎完成计算(查询)时,该项可在UI中连接,并且可能的连接路径存储在本地变量中以供将来使用(例如,当用户单击以连接项时,所有可能的路径将是根据添加时计算的内容突出显示)
  5. (删除项目时必须执行类似的过程)

4 个答案:

答案 0 :(得分:4)

编辑:这当前不起作用,因为查询表达式不允许使用大括号。编辑...

你总是可以添加一个“no-op”select或where子句来显示进度:

public class ProgressCounter
{
    private readonly int total;
    private int count;
    private int lastPercentage;

    public ProgressCounter(int total)
    {
        this.total = total;
    }

    public void Update()
    {
        count++;
        int currentPercentage = (count * 100) / total;
        if (currentPercentage != lastPercentage)
        {
            Console.WriteLine("Done {0}%", currentPercentage);
            lastPercentage = currentPercentage;
        }
        return true;
    }
}

...

var progressCounter = new ProgressCounter(A.Count * B.Count);

var pairs = from car in A
            from truck in B
            where progressCounter.Update()
            where car.color==truck.color
            select new {car, truck};

注意使用副作用,这总是令人讨厌。我希望你使用联接,如果这真的是查询,顺便说一下:)

我们一直在考虑将这类运算符添加到MoreLINQ - 称为Pipe,Apply,Via或类似的东西。

答案 1 :(得分:2)

我使用过的效果很好的是DataContext的一个适配器,它返回了它产生的项目数的计数。

public class ProgressArgs : EventArgs
{
    public ProgressArgs(int count)
    {
        this.Count = count;
    }

    public int Count { get; private set; }
}

public class ProgressContext<T> : IEnumerable<T>
{
    private IEnumerable<T> source;

    public ProgressContext(IEnumerable<T> source)
    {
        this.source = source;
    }

    public event EventHandler<ProgressArgs> UpdateProgress;

    protected virtual void OnUpdateProgress(int count)
    {
        EventHandler<ProgressArgs> handler = this.UpdateProgress;
        if (handler != null)
            handler(this, new ProgressArgs(count));
    }

    public IEnumerator<T> GetEnumerator()
    {
        int count = 0;
        foreach (var item in source)
        {
            // The yield holds execution until the next iteration,
            // so trigger the update event first.
            OnUpdateProgress(++count);
            yield return item;
        }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

用法

var context = new ProgressContext(
    from car in A 
    from truck in B 
    select new {car, truck};
);
context.UpdateProgress += (sender, e) =>
{
    // Do your update here
};

var query = from item in context
            where item.car.color==item.truck.color;

// This will trigger the updates
query.ToArray();

唯一的问题是除非你知道总数,否则你不能轻易做一个百分比。要进行总计数通常需要处理整个列表,这可能是昂贵的。如果您事先知道总计数,那么您可以在UpdateProgress事件处理程序中计算出一个百分比。

答案 2 :(得分:1)

Linq的大部分内容都是使用延迟评估完成的。因此,在您对结果进行预测之前,实际上不会执行查询。每次从对中“拉”结果时,都会评估一部分查询。

这意味着你可以在foreach循环中显示循环,迭代结果。缺点是您事先并不知道结果集的大小,计算结果集的大小也会迭代结果并执行查询。

答案 3 :(得分:0)

虽然我确信他的方法会更加简洁,但我和Jon的相似。你可以用同样的方法一起破解一些东西..

var pairs = from car in A
            from truck in B
            let myProgress = UpdateProgress(...)
            where car.color == truck.color
            select new { car, truck };

private int UpdateProgress(...)
{
    Console.WriteLine("Updating Progress...");
    return -1;
}

尽管如上所述,在迭代之前不会执行查询。 这也是在查询中创建新范围变量的另一个缺点。