WPF: Как вызывать Dispatcher.BeginInvoke * только *, когда в очереди ничего не вызывается? - PullRequest
1 голос
/ 17 декабря 2010

У меня есть метод импорта файла в приложении WPF, который читает файл и вставляет некоторые записи в БД.

Этот метод выполняется в объекте BackgroundWorker. У меня есть индикатор выполнения, который обновляется при вызове Dispatcher.Invoke. Если я запускаю как есть, импорт 200k записей занимает ~ 1 минуту, если я просто не показываю никакого прогресса, это занимает всего 4-5 секунд! И если я использую Dispatcher.BeginInvoke с приоритетом Background, это займет те же 4-5 секунд, но индикатор выполнения + счетчик обновляются и занимают ~ 1 минуту. Итак, очевидно, что здесь проблема с пользовательским интерфейсом.

И другая проблема в том, что мне нужно показать прогресс, поэтому я подумал, есть ли способ использовать Dispatcher.BeginInvoke, но сначала проверь, есть ли что-нибудь в очереди, и если да, я просто пропускаю это, что будет вести себя так: в 1-й секунде выполнено 1%, через 2 секунды выполнено 50%, а в 4-ю секунду выполнено 100%).

Любая помощь в этом?

спасибо !!!

Ответы [ 4 ]

2 голосов
/ 17 декабря 2010

Проблема в том, что ваши обратные вызовы стоят в очереди в Диспетчере. Каждый вызовет перерисовку экрана, и поскольку они имеют приоритет фона, следующий будет ждать завершения этой перерисовки перед обработкой, поэтому вам придется перекрашивать один раз для каждого обратного вызова, что может быть медленным.

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

Вы можете сделать это, установив флажок при публикации обратного вызова и очистив его после его обработки. Например:

private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
    var pending = false;
    for (int i = 0; i < 1000000; i++)
    {
        // Do some work here
        // ...
        // Only report progress if there is no progress report pending
        if (!pending)
        {
            // Set a flag so we don't post another progress report until
            // this one completes, and then post a new progress report
            pending = true;
            var currentProgress = i;
            Dispatcher.BeginInvoke(new Action(() =>
            {
                // Do something with currentProgress
                progressBar.Value = currentProgress;
                // Clear the flag so that the BackgroundWorker
                // thread will post another progress report
                pending = false;
            }), DispatcherPriority.Background);
        }
    }
}
2 голосов
/ 09 марта 2011

Я бы просто обновил счетчик хода выполнения в фоновом потоке (он только записывает данные в счетчик), и пользовательский интерфейс считывал (только читал) таймер каждые 500 мс или около того ... Нет причин обновляться быстрее, чем тот. Кроме того, поскольку один поток предназначен только для записи, а другой - только для чтения, проблем с потоками не требуется. Код становится значительно проще, понятнее и удобнее в обслуживании.

-Черт Пеллетт

2 голосов
/ 17 декабря 2010

Невозможно сказать, не видя код, но

У меня есть индикатор выполнения в вызове Dispatcher.Invoke

Почему? Вот для чего ReportProgress.

Если бы мне пришлось угадывать (и я делаю), я бы сказал, что вы часто сообщаете о прогрессе. Например, не сообщайте о прогрессе после каждой записи, но после 100 или более партий.

0 голосов
/ 04 июня 2016

Я только что решил тот же случай, но с использованием объекта, возвращенного BeginInvoke, и я думаю, что это тоже довольно элегантно!

DispatcherOperation uiOperation = null;
while (…)
{
    …
    if (uiOperation == null || uiOperation.Status == DispatcherOperationStatus.Completed || uiOperation.Status == DispatcherOperationStatus.Aborted)
    {
        uiOperation = uiElement.Dispatcher.BeginInvoke(…);
    }
}

Индикаторы выполнения становятся немного более прерывистыми (менее плавными), ноэто летает.В моем случае код разбирает построчно текстовый файл, используя StreamReader.ReadLine().Обновление индикатора выполнения после чтения каждой строки приведет к завершению операций чтения до того, как индикатор выполнения будет заполнен хотя бы наполовину.Использование синхронного Dispatcher.Invoke(…) замедлит всю операцию до 100 КиБ / с, но индикатор выполнения будет точно отслеживать прогресс.Используя приведенное выше решение, мое приложение завершило синтаксический анализ 8000 КиБ в секунду всего за 3 обновления индикатора выполнения.

Одно отличие от использования BackgroundWorker.ReportProgress(…) состоит в том, что индикатор выполнения может отображать более мелкие детали при более длительных операциях.BackgroundWorker.ReportProgress(…) ограничивается сообщением о прогрессе с шагом 1% от 0% до 100%.Если ваш индикатор выполнения представляет более 100 операций, более точные значения желательны.Конечно, этого также можно добиться, не используя аргумент percentProgress и передав вместо userState BackgroundWorker.ReportProgress(…).

...