Автоматизация шаблона кода InvokeRequired

179

Я стал мучительно осознавать, как часто нужно писать следующий шаблон кода в коде 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 () неэффективен и по- прежнему неудобен в обращении.

Итак, кто-нибудь разобрался в каких-либо сочетаниях клавиш?

Том Корелис
источник
2
Я удивился тому же, но в отношении Dispatcher.CheckAccess () в WPF.
Тейлор Лиз,
Я придумал довольно сумасшедшее предложение, вдохновленное вашей object1.InvokeIfNecessary.Visible = trueлинией; Проверьте мой обновленный ответ и дайте мне знать, что вы думаете.
Дэн Тао
1
Добавьте фрагмент кода, чтобы помочь реализовать метод, предложенный Мэттом Дэвисом: посмотрите мой ответ (поздно, но просто показываю, как для более поздних читателей ;-))
Аарон Гейдж
3
Я не понимаю, почему Microsoft не сделала ничего, чтобы упростить это в .NET. Создание делегатов для каждого изменения в форме из потока действительно раздражает.
Камил
@ Камил, я не могу не согласиться! Это такой недосмотр, учитывая его повсеместность. В рамках, просто обрабатывайте потоки, если это необходимо. Кажется очевидным.
SteveCinq

Ответы:

138

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

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 # автоматически создает замыкание .


ОБНОВЛЕНИЕ :

По нескольким другим постерам 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
Не лучше ли иметь ISynchronizeInvokeвместо Control? (Слава Джону Скиту stackoverflow.com/questions/711408/… )
Одис
@odyodyodys: Хорошая мысль. Я не знал о ISynchronizeInvoke. Но единственный тип, который вытекает из этого (согласно Reflector) Control, так что преимущество ограничено.
Оливье Жако-Дескомб,
3
@ mike-de-clerk, я обеспокоен твоим предложением добавить while (!control.Visible) ..sleep... Для меня это имеет неприятный запах кода, поскольку это потенциально неограниченная задержка (может быть, даже бесконечный цикл в некоторых случаях) в коде, который может иметь вызывающих абонентов, которые не ожидают такой задержки (или даже тупика). ИМХО, любое использование Sleepдолжно быть ответственностью каждого звонящего, ИЛИ должно быть в отдельной оболочке, которая четко помечена относительно его последствий. ИМХО, обычно было бы лучше «сильно потерпеть неудачу» (исключение, чтобы поймать во время тестирования), или «ничего не делать», если элемент управления не готов. Комментарии?
ToolmakerSteve
1
@ OlivierJacot-Descombes, было бы здорово, если бы вы объяснили, как работает thread.invokerequired?
Sudhir.net
1
InvokeRequiredсообщает, отличается ли вызывающий поток от потока, создавшего элемент управления. Invokeпередает действие из вызывающего потока в поток элемента управления, где оно выполняется. Это гарантирует, что, например, обработчик события щелчка никогда не прерывается.
Оливье Жако-Дескомб