Автоматизація шаблону коду 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.