Автоматизація шаблону коду 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. Створення делегатів для кожної зміни форми на нитці насправді дратує.
— Каміль

@Kamil Я не могла погодитися більше! Це такий нагляд, враховуючи всю його повсюдність. У рамках просто обробіть різьблення, якщо це необхідно. Здається, очевидним.
— 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

Зміни, запропоновані Майком де Клерком (див. Коментар у фрагменті першого коду для точки вставки):

// 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? (Kudos to Jon Skeet stackoverflow.com/questions/711408/… )
— Одіс

@odyodyodys: Добре. Я про це не знав ISynchronizeInvoke. Але єдиним типом, який випливає з нього (за Рефлектором) є Control, тому адаптованість обмежена.
— Олів'є Якот-Дескомб

3
@ Майк-де-клерк, я стурбований вашою пропозицією додати while (!control.Visible) ..sleep... Для мене поганий запах коду, оскільки це потенційно необмежена затримка (можливо, навіть нескінченна петля в деяких випадках), в коді може бути абонентів, які не очікують такої затримки (або навіть тупикової ситуації). ІМХО, будь-яке використання Sleepповинно бути відповідальним за кожного абонента, АБО має бути в окремій обгортці, яка чітко позначена щодо її наслідків. ІМХО, як правило, було б краще "важко вийти з ладу" (виняток, щоб зловити під час тестування) або "нічого не робити", якщо контроль не готовий. Коментарі?
— ToolmakerSteve

1
@ OlivierJacot-Descombes, Було б чудово, якщо ви, будь ласка, поясніть, як за темою працює thread.invokerequire?
— Sudhir.net

1
InvokeRequiredповідомляє, чи відрізняється викликає потік від потоку, який створив елемент управління. Invokeпередає дію від викличного потоку до потоку управління, де воно виконується. Це гарантує, що, наприклад, обробник подій клацання ніколи не переривається.
— Олів'є Якот-Дескомбс

133

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

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

EDIT: Як в коментарях вказує Сімпсон, ви також можете змінити підпис на:

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

Можливо, я занадто німий, але цей код не збирається. Тому я виправив це так, як він побудований мною (VS2008).
— Олівер

5
Просто для повноти: У WPF є інший механізм диспетчеризації, але він працює досить аналогічно. Ви можете використати цей метод розширення там: public static void InvokeIfRequired <T> (це T aTarget, Action <T> aActionToExecute), де T: DispatcherObject {if (aTarget.CheckAccess ()) {aActionToExecute (aTarget); } else {aTarget.Dispatcher.Invoke (aActionToExecute); }}
— Саймон Д.

1
Я додав відповідь, яка трохи спрощує рішення Лі.
— Олів'є Якот-Дескомбс

Привіт, оскільки я використовую щось подібне, може виникнути велика проблема, що випливає з цієї загальної реалізації. Якщо елемент управління розпоряджається / розпоряджається, ви отримаєте ObjectDisposedException.
— Відкриття

1
@Offler - Ну, якщо вони розміщені в іншому потоці, у вас є проблема синхронізації, це не проблема в цьому методі.
— Лі

33

Ось форма, яку я використовував у своєму коді.

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

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

Сподіваюся, це допомагає.


+1 - Я натрапив на той самий запис у блозі, який ви робили, і думаю, що це найчистіший підхід із будь-якого запропонованого
— Том Бушелл

3
За допомогою цього підходу є невеликий хіт на ефективність, який може накопичуватися при виклику декількох разів. stackoverflow.com/a/747218/724944
— сурфен

4
Ви повинні використовувати, InvokeRequiredякщо код міг бути виконаний до того, як було показано контроль, або у вас буде фатальний виняток.
— 56ка

9

Створіть файл 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

Ось вдосконалена / комбінована версія відповідей Лі, Олівера та Стефана.

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

4

Я вважаю за краще використовувати один екземпляр методу 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);
}

3

Використання:

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

2

Мені б хотілося зробити це трохи інакше, я люблю називати "себе", якщо потрібно,

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


-3

Ніколи не слід писати код, який виглядає приблизно так:

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


Гей, дякую за вашу відповідь. Минуло роки, як я задав це питання (і майже так само довго, як я працював із C #), але мені було цікаво, чи можете ви пояснити трохи далі? Документи, з якими ви посилаєтесь, посилаються на конкретну небезпеку виклику invoke()та ін до того, як керування отримає ручку, але IMHO не описує те, що ви описали. Весь сенс усієї цієї invoke()дурниці полягає в тому, щоб оновити інтерфейс користувача захищеним від потоку, і я думаю, що введення додаткових інструкцій у блокуючий контекст призведе до заїкання? (Тьфу ... рада, що я перестав використовувати M $ tech. Настільки складний!)
— Том Кореліс

Я також хочу зазначити, що незважаючи на часте використання оригінального коду (зворотній час, коли), я не спостерігав проблеми, яку ви описали на моєму робочому столі з подвійним процесором
— Tom Corelis

3
Я сумніваюся, що ця відповідь є точною, оскільки MSDN показує безліч прикладів, як і ОП.
— загальнодоступний бездротовий зв'язок
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.