Автоматизация шаблона кода InvokeRequired - PullRequest
175 голосов
/ 03 марта 2010

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

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

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

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

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

Ответы [ 9 ]

133 голосов
/ 03 марта 2010

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

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
129 голосов
/ 29 августа 2012

Подход Ли может быть еще более упрощен

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

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


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 ниже для беспокойства по поводу этого предложения.

33 голосов
/ 03 марта 2010

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

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

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

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

9 голосов
/ 15 февраля 2011

Создайте файл 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>
6 голосов
/ 07 апреля 2015

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

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;
});
3 голосов
/ 02 октября 2017

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

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

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

    return c.Visible;
});

Код:

using System;
using System.ComponentModel;

namespace Extensions
{
    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
        {
            return obj.InvokeRequired
                ? (TOut)obj.Invoke(func, new object[] { obj })
                : func(obj);
        }
    }
}
3 голосов
/ 21 июня 2013

Я бы предпочел использовать один экземпляр метода Delegate вместо того, чтобы каждый раз создавать новый экземпляр. В моем случае я использовал для отображения сообщений о ходе выполнения и (информация / ошибка) от 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);
}
2 голосов
/ 22 декабря 2016

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

    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, когда закрываю свою форму, поскольку могут быть некоторые фоновые потоки, которые все еще работают ...

0 голосов
/ 29 октября 2014

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

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 будет считать, что поток пользовательского интерфейса завершен, и завершит процесс приложения, т. е. ваше приложение завершит работу без ошибок.

...