Как сообщить о прогрессе при выполнении выражения LINQ для набора данных большого размера - PullRequest
5 голосов
/ 17 марта 2009

Если мне нужно сгенерировать довольно большой набор данных с помощью LINQ, и это может занять некоторое время (скажем, несколько секунд), и мне нужно (хотелось бы) создать обратную связь для использования в отношении% выполненного возраста, есть ли / предпочтительный способ сделать это?

Например, скажем, у меня есть список A с 1000 легковыми автомобилями и список B с 1000 грузовыми автомобилями, и я хочу выбрать все возможные заказанные пары (легковые и грузовые автомобили), где car.color == truck.color ссылаются на это:

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

Теперь в какой-то момент это будет оцениваться как набор вложенных циклов foreach. Я хотел бы иметь возможность сообщать% 'age complete, как он есть, и в идеале обновлять индикатор выполнения или что-то в этом роде.

РЕДАКТИРОВАТЬ: Сразу после запроса я сохраняю результат в переменной-члене в виде списка, подобного этому (который заставляет выполнить запрос):

mPairs = pairs.ToList();

Я делаю это, потому что я выполняю это в фоновом рабочем потоке, поскольку я не хочу, чтобы поток пользовательского интерфейса зависал, поскольку он оценивает выражение LINQ по требованию в потоке пользовательского интерфейса (это в Silverlight BTW). Вот почему я хотел бы сообщить о прогрессе. UX в основном это:

  1. Пользователь перетаскивает элемент в рабочую область
  2. Затем движок запускает фоновый поток, чтобы определить (многие) возможности подключения ко всем остальным элементам в рабочей области.
  3. Во время вычисления движка пользовательский интерфейс не позволяет новым соединениям И сообщает о ходе выполнения, чтобы указать, когда новый элемент будет «подключаемым» к другим элементам (все возможные пути подключения, которые еще не используются, были определены через LINQ).
  4. Когда механизм завершает вычисление (запрос), элемент подключается в пользовательском интерфейсе, и возможные пути подключения сохраняются в локальной переменной для будущего использования (например, когда пользователь щелкает, чтобы подключить элемент, все возможные пути будут подсвечивается на основе того, что было рассчитано при добавлении)

(аналогичный процесс должен произойти при удалении элемента)

Ответы [ 4 ]

4 голосов
/ 17 марта 2009

РЕДАКТИРОВАТЬ: Это в настоящее время не работает, потому что выражения запроса не позволяют фигурные скобки. Редактирование ...

Вы всегда можете добавить предложение «no-op» или предложение 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 или что-то в этом роде.

2 голосов
/ 18 марта 2009

Что-то, что я использовал, что работало хорошо, было адаптером для 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.

1 голос
/ 17 марта 2009

Большая часть Linq выполняется с использованием ленивых вычислений. Таким образом, запрос фактически не выполняется, пока вы не определите результат. Каждый раз, когда вы «извлекаете» результат из пар, оценивается часть запроса.

Это означает, что вы можете просто отображать прогресс в цикле foreach, который перебирает результат. Недостатком является то, что вы заранее не знаете, насколько большим будет набор результатов, подсчет размера набора результатов также будет повторять результаты и выполнять запрос.

0 голосов
/ 17 марта 2009

Ну, мой был похож на Джон, хотя я уверен, что его подход был бы гораздо более кратким. Вы можете взломать что-нибудь вместе одним и тем же способом.

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

Хотя, как уже упоминалось, запрос не будет выполнен, пока не будет повторен. Это также является дополнительным недостатком создания новой переменной области действия внутри запроса.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...