Обновление GUI, пока другие потоки работают без изменения потока - C# - PullRequest
0 голосов
/ 29 февраля 2020

Я должен сообщить о некоторых определенных вещах своему GUI, пока в фоновом режиме работает другой поток, например:

  • Значение прогресса
  • Истекшее время
  • Количество результатов, найденных в режиме реального времени
  • Количество ошибок, возникших в процессе
  • и т. Д.

Я могу использовать этот фрагмент кода, когда мне нужно чтобы вызвать пользовательский интерфейс и что-то изменить:

    private void DoInvoke(Action action)
    {
        try
        {
            if (InvokeRequired)
                BeginInvoke(action);
            else
                action();
        }
        catch { }
    }

Это работает хорошо, GUI и фоновый поток работают очень хорошо, и информация будет сообщаться и отображаться в пользовательском интерфейсе. Но есть проблема: из-за большого количества контекстов, изменяющихся между фоновым потоком и пользовательским интерфейсом, загрузка ЦП будет очень высокой! Мне нужно обновить значения пользовательского интерфейса без изменения этого контекста и без использования процессора.

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

, и я поместил обработчик событий в класс, поэтому при каждом изменении значения он будет вызываться. в пользовательском интерфейсе я прикрепил событие к этому обработчику, поэтому каждый раз, когда значение изменяется в этом классе, пользовательский интерфейс должен обновить это значение. Но я снова столкнусь с cross-thread error. Как справиться с такой вещью? Я не хочу использовать процессор с высокой нагрузкой, а также мне нужно обновление интерфейса пользователя в режиме реального времени.

1 Ответ

1 голос
/ 29 февраля 2020

Существуют различные способы решения этой проблемы.

Первое, что нужно определить - это «в реальном времени». Если ваши данные изменяются каждую 1 миллисекунду, даже если вы смогли обновить интерфейс так быстро, никто не сможет его увидеть. В качестве ориентира мы можем обнаруживать изменения только на частоте около 60 Гц (поэтому видеоигры ориентированы на эту частоту кадров). На практике вы, вероятно, хотите, чтобы пользовательский интерфейс обновлялся в диапазоне 10–50 Гц.

Решение с таймером

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

Решение Invoke () / BeginInvoke ()

Еще один вариант - по-прежнему использовать BeginInvoke ( ), но:

  1. Реализовать logi c для обновления всех элементов управления в одной функции и только BeginInvoke () этой, так что вы ставите в очередь только один рабочий элемент в пользовательском интерфейсе нить. Если бы вы делали BeginInvoke () для каждого элемента управления, вы бы вызвали переключение контекста для каждого элемента управления.

  2. Пропустить вызов BeginInvoke (), если минимальное время не истекло с момента последнее обновление Например, если данные изменились через 3 миллисекунды, вы можете пропустить все обновления, пока они не произойдут через 50 миллисекунд (что даст максимальную частоту обновления 20 Гц).

Осложнения

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

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

Итак, в моих программах я обычно устанавливаю флаг непосредственно перед вызовом BeginInvoke (), и я очищаю его в вызываемой функции. В следующий раз, когда мне нужно вызвать BeginInvoke (), я сначала проверяю флаг. Если он все еще установлен, это означает, что поток пользовательского интерфейса был занят и все еще не смог обновить пользовательский интерфейс. В этом случае я пропускаю BeginInvoke ().

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

Заключительные примечания

Если значение не имеет изменилось, вы хотите избежать перерисовки относительного контроля, потому что это бессмысленно. Я ожидаю, что большинство элементов управления WinForms, таких как метка, уже не будут перерисовываться, если вы установите для их текста то же значение, которое у них уже есть, но если у вас есть пользовательские элементы управления, сторонние элементы управления или такие вещи, как очистка ListView и повторное его заполнение, вы хочу убедиться, что код не вызывает перерисовку, когда он не нужен.

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