Автоматизация кода кода InvokeRequired

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

private void DoGUISwitch() {
    // cruisin for a bruisin' through exception city
    object1.Visible = true;
    object2.Visible = false;
}

становится:

private void DoGUISwitch() {
    if (object1.InvokeRequired) {
        object1.Invoke(new MethodInvoker(() => { DoGUISwitch(); }));
    } else {
        object1.Visible = true;
        object2.Visible = false;
    }
}

Это неудобный шаблон в С#, как для запоминания, так и для ввода. Кто-нибудь придумал какой-то ярлык или конструкцию, которая автоматизирует это до определенной степени? Было бы здорово, если бы был способ привязать функцию к объектам, выполняющим эту проверку, без необходимости выполнять всю эту дополнительную работу, например, ярлык типа object1.InvokeIfNecessary.visible = true.

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

Итак, кто-нибудь понял какие-нибудь ярлыки?

Ответ 1

Ли подход можно упростить далее

public static void InvokeIfRequired(this Control control, MethodInvoker action)
{
    // See Update 2 for edits Mike de Klerk suggests to insert here.

    if (control.InvokeRequired) {
        control.Invoke(action);
    } else {
        action();
    }
}

И можно называть это

richEditControl1.InvokeIfRequired(() =>
{
    // Do anything you want with the control here
    richEditControl1.RtfText = value;
    RtfHelpers.AddMissingStyles(richEditControl1);
});

Нет необходимости передавать элемент управления в качестве параметра для делегата. С# автоматически создает closure.


UPDATE

В соответствии с несколькими другими плакатами Control можно обобщить как ISynchronizeInvoke:

public static void InvokeIfRequired(this ISynchronizeInvoke obj,
                                         MethodInvoker action)
{
    if (obj.InvokeRequired) {
        var args = new object[0];
        obj.Invoke(action, args);
    } else {
        action();
    }
}

DonBoitnott отметил, что в отличие от Control для интерфейса ISynchronizeInvoke требуется массив объектов для метода Invoke в качестве списка параметров для action.


ОБНОВЛЕНИЕ 2

Редактирование, предложенное Майком де Клерком (см. комментарий в 1-м фрагменте кода для точки вставки):

// When the form, thus the control, isn't visible yet, InvokeRequired  returns false,
// resulting still in a cross-thread exception.
while (!control.Visible)
{
    System.Threading.Thread.Sleep(50);
}

См. комментарий к ToolmakerSteve ниже для беспокойства по поводу этого предложения.

Ответ 2

Вы можете написать метод расширения:

public static void InvokeIfRequired(this Control c, Action<Control> action)
{
    if(c.InvokeRequired)
    {
        c.Invoke(new Action(() => action(c)));
    }
    else
    {
        action(c);
    }
}

И используйте его следующим образом:

object1.InvokeIfRequired(c => { c.Visible = true; });

ИЗМЕНИТЬ: Как указывает Симпзон в комментариях, вы также можете изменить подпись:

public static void InvokeIfRequired<T>(this T c, Action<T> action) 
    where T : Control

Ответ 3

Здесь форма, которую я использовал во всем моем коде.

private void DoGUISwitch()
{ 
    Invoke( ( MethodInvoker ) delegate {
        object1.Visible = true;
        object2.Visible = false;
    });
} 

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

Надеюсь, что это поможет.

Ответ 4

Создайте файл ThreadSafeInvoke.snippet, а затем вы можете просто выбрать операторы обновления, щелкнуть правой кнопкой мыши и выбрать "Surround With..." или Ctrl-K + S:

<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippet Format="1.0.0" xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <Header>
    <Title>ThreadsafeInvoke</Title>
    <Shortcut></Shortcut>
    <Description>Wraps code in an anonymous method passed to Invoke for Thread safety.</Description>
    <SnippetTypes>
      <SnippetType>SurroundsWith</SnippetType>
    </SnippetTypes>
  </Header>
  <Snippet>
    <Code Language="CSharp">
      <![CDATA[
      Invoke( (MethodInvoker) delegate
      {
          $selected$
      });      
      ]]>
    </Code>
  </Snippet>
</CodeSnippet>

Ответ 5

Здесь представлена ​​улучшенная/комбинированная версия Ли, Оливера и Стефана.

public delegate void InvokeIfRequiredDelegate<T>(T obj)
    where T : ISynchronizeInvoke;

public static void InvokeIfRequired<T>(this T obj, InvokeIfRequiredDelegate<T> action)
    where T : ISynchronizeInvoke
{
    if (obj.InvokeRequired)
    {
        obj.Invoke(action, new object[] { obj });
    }
    else
    {
        action(obj);
    }
} 

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

progressBar1.InvokeIfRequired(o => 
{
    o.Style = ProgressBarStyle.Marquee;
    o.MarqueeAnimationSpeed = 40;
});

Ответ 6

Я предпочел бы использовать единственный экземпляр метода Delegate вместо создания нового экземпляра каждый раз. В моем случае я использовал для отображения сообщений о прогрессе и (info/error) от копирования Backroundworker и отливки больших данных из экземпляра sql. Каждый раз после примерно 70000 хода выполнения и сообщений моя форма переставала работать и показывала новые сообщения. Это не произошло, когда я начал использовать один глобальный делегат экземпляра.

delegate void ShowMessageCallback(string message);

private void Form1_Load(object sender, EventArgs e)
{
    ShowMessageCallback showMessageDelegate = new ShowMessageCallback(ShowMessage);
}

private void ShowMessage(string message)
{
    if (this.InvokeRequired)
        this.Invoke(showMessageDelegate, message);
    else
        labelMessage.Text = message;           
}

void Message_OnMessage(object sender, Utilities.Message.MessageEventArgs e)
{
    ShowMessage(e.Message);
}

Ответ 7

Применение:

control.InvokeIfRequired(c => c.Visible = false);

return control.InvokeIfRequired(c => {
    c.Visible = value

    return c.Visible;
});

Код:

public static class SynchronizeInvokeExtensions
{
    public static void InvokeIfRequired<T>(this T obj,
        Action<T> action) where T : ISynchronizeInvoke
    {
        if (obj.InvokeRequired)
            obj.Invoke(action, new object[] { obj });
        else
            action(obj);
    }

    public static TOut InvokeIfRequired<TIn, TOut>(this TIn obj,
        Func<TIn, TOut> func) where TIn : ISynchronizeInvoke => 
        obj.InvokeRequired ? (TOut)obj.Invoke(func, new object[] { obj }) : func(obj);
}

Ответ 8

Мне нравится делать это немного по-другому, мне нравится называть "себя", если нужно, с помощью Action,

    private void AddRowToListView(ScannerRow row, bool suspend)
    {
        if (IsFormClosing)
            return;

        if (this.InvokeRequired)
        {
            var A = new Action(() => AddRowToListView(row, suspend));
            this.Invoke(A);
            return;
        }
         //as of here the Code is thread-safe

это удобный шаблон, IsFormClosing - это поле, которое я устанавливаю в True, когда закрываю форму, поскольку могут быть некоторые фоновые потоки, которые все еще работают...

Ответ 9

Вы никогда не должны писать код, который выглядит так:

private void DoGUISwitch() {
    if (object1.InvokeRequired) {
        object1.Invoke(new MethodInvoker(() => { DoGUISwitch(); }));
    } else {
        object1.Visible = true;
        object2.Visible = false;
    }
}

Если у вас есть код, который выглядит так, то ваше приложение не является потокобезопасным. Это означает, что у вас есть код, который уже вызывает DoGUISwitch() из другого потока. Это слишком поздно, чтобы проверять, есть ли это в другом потоке. InvokeRequire должен быть вызван ПЕРЕД вызовом DoGUISwitch. Вы не должны обращаться к каким-либо методам или свойствам из другого потока.

Ссылка: Свойство Control.InvokeRequired где вы можете прочитать следующее:

В дополнение к свойству InvokeRequired существует четыре метода элемент управления, который безопасен для потока: Invoke, BeginInvoke, EndInvoke и CreateGraphics, если дескриптор элемента управления уже был создан.

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