Очистка кода, заваленного InvokeRequired

Я знаю, что при манипулировании элементами управления UI из любого потока, отличного от UI, вы должны маршалировать свои вызовы в поток UI, чтобы избежать проблем. Общий консенсус заключается в том, что вы должны использовать test InvokeRequired, и если true, используйте .Вызов для выполнения маршалинга.

это приводит к большому количеству кода, который выглядит так:

private void UpdateSummary(string text)
{
    if (this.InvokeRequired)
    {
        this.Invoke(new Action(() => UpdateSummary(text)));
    }
    else
    {
        summary.Text = text;
    }
}

мой вопрос таков: могу ли я оставить тест InvokeRequired и просто вызвать Invoke, например:

private void UpdateSummary(string text)
{
    this.Invoke(new Action(() => summary.Text = text));
}

есть ли проблема с делать это? Если да, есть ли лучший способ сохранить тест InvokeRequired, не копируя и не вставляя этот шаблон повсюду?

8 ответов


Ну как насчет этого:

public static class ControlHelpers
{
    public static void InvokeIfRequired<T>(this T control, Action<T> action) where T : ISynchronizeInvoke
    {
        if (control.InvokeRequired)
        {
            control.Invoke(new Action(() => action(control)), null);
        }
        else
        {
            action(control);
        }
    }
}

используйте его так:

private void UpdateSummary(string text)
{
    summary.InvokeIfRequired(s => { s.Text = text });
}

вызов Invoke из потока пользовательского интерфейса несколько неэффективен.

вместо этого вы можете создать InvokeIfNeeded метод расширения, который принимает


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

результаты могут удивить некоторых из вас (эти тесты были выполнены через


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

Mine немного отличается тем, что он может немного более безопасно обрабатывать нулевые элементы управления и при необходимости возвращать результаты. Оба из них пригодились мне при попытке вызвать отображение MessageBox в родительской форме, которая может быть null, и возвращая DialogResult показа этого Функции MessageBox.


using System;
using System.Windows.Forms;

/// <summary>
/// Extension methods acting on Control objects.
/// </summary>
internal static class ControlExtensionMethods
{
    /// <summary>
    /// Invokes the given action on the given control's UI thread, if invocation is needed.
    /// </summary>
    /// <param name="control">Control on whose UI thread to possibly invoke.</param>
    /// <param name="action">Action to be invoked on the given control.</param>
    public static void MaybeInvoke(this Control control, Action action)
    {
        if (control != null && control.InvokeRequired)
        {
            control.Invoke(action);
        }
        else
        {
            action();
        }
    }

    /// <summary>
    /// Maybe Invoke a Func that returns a value.
    /// </summary>
    /// <typeparam name="T">Return type of func.</typeparam>
    /// <param name="control">Control on which to maybe invoke.</param>
    /// <param name="func">Function returning a value, to invoke.</param>
    /// <returns>The result of the call to func.</returns>
    public static T MaybeInvoke<T>(this Control control, Func<T> func)
    {
        if (control != null && control.InvokeRequired)
        {
            return (T)(control.Invoke(func));
        }
        else
        {
            return func();
        }
    }
}

использование:

myForm.MaybeInvoke(() => this.Text = "Hello world");

// Sometimes the control might be null, but that's okay.
var dialogResult = this.Parent.MaybeInvoke(() => MessageBox.Show(this, "Yes or no?", "Choice", MessageBoxButtons.YesNo));

Я не уверен, что Control.Invoke является лучшим выбором для обновления пользовательского интерфейса. Я не могу сказать наверняка в вашем случае, потому что я не знаю обстоятельств, при которых UpdateSummary в называется. Однако, если вы периодически вызываете его как механизм для отображения информации о ходе работы (это впечатление, которое я получаю из фрагмента кода), то есть обычно лучший вариант. Этот параметр должен иметь опрос потока пользовательского интерфейса для состояния, а не толкать рабочий поток он.

причины, по которым подход опроса должен быть рассмотрен в этом случае, заключаются в том, что:

  • это разрывает плотную связь между UI и рабочими потоками, которые Control.Invoke навязывает.
  • он возлагает ответственность за обновление потока пользовательского интерфейса на поток пользовательского интерфейса, где он должен принадлежать в любом случае.
  • поток пользовательского интерфейса получает диктовать, когда и как часто должно происходить обновление.
  • нет никакого риска насоса сообщений пользовательского интерфейса переполнение, как и в случае с методами маршалинга, инициированными рабочим потоком.
  • рабочему потоку не нужно ждать подтверждения того, что обновление было выполнено, прежде чем приступить к следующим шагам (т. е. вы получаете больше пропускной способности как в UI, так и в рабочих потоках).

поэтому подумайте о создании System.Windows.Forms.Timer, которая периодически проверяет текст, который будет отображаться на Control вместо того, чтобы инициировать толчок из рабочего потока. Опять же, не зная ваших точных требований, я не готов сказать это определенно направление, в котором вам нужно идти, но в большинство многих случаях это и лучше, чем .

очевидно, что этот подход устраняет необходимость InvokedRequired полностью проверить. Неважно, что это просто все другие аспекты взаимодействия UI / рабочего потока.


мой предпочтительный подход к элементам управления только для просмотра - иметь все состояние элемента управления, инкапсулированное в класс, который может быть обновлен без каких-либо несогласованных состояний (простой способ сделать это-поместить все вещи, которые должны быть обновлены вместе в неизменяемый класс, и создать новый экземпляр класса всякий раз, когда требуется обновление). Тогда есть метод, который будет взаимосвязан.Обменяйте флаг updateNeeded и, если обновление не ожидает, но IsHandleCreated true, затем BeginInvoke процедуру обновления. Процедура обновления должна очистить флаг updateNeeded как первое, что он делает, прежде чем делать какие-либо обновления (если кто-то пытается обновить элемент управления в этот момент, другой запрос будет BeginInvoked). Обратите внимание, что вы должны быть готовы поймать и проглотить исключение (я думаю, IllegalOperation), если элемент управления будет удален так же, как вы готовитесь его обновить.

кстати, если элемент управления еще не был присоединен к потоку (путем добавлено в видимое окно или окно, в котором оно стало видимым), законно обновлять его напрямую, но не законно использовать BeginInvoke или вызывать его.


Я пока не могу комментировать, надеюсь, кто-то увидит это и добавит его к принятому ответу, который в противном случае находится на месте.

control.Invoke(new Action(() => action(control))); следует читать
control.Invoke(new Action(() => action(control)), null);

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

еще одна вещь заключается в том, что использование может быть более ясным, как
summary.InvokeIfRequired(c => { summary.Text = text; });, а не как написано summary.InvokeIfRequired(c => { textBox.Text = text });


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