Автоматизация шаблона кода InvokeRequired
Я стал мучительно осознавать, как часто нужно писать следующий шаблон кода в коде 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 ответов
Подход Ли может быть упрощен в дальнейшем
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 ниже для беспокойства по поводу этого предложения.
Вы можете написать метод расширения:
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; });
РЕДАКТИРОВАТЬ: Как Simpzon указывает в комментариях, вы также можете изменить подпись:
public static void InvokeIfRequired<T>(this T c, Action<T> action)
where T : Control
Вот форма, которую я использовал во всем своем коде.
private void DoGUISwitch()
{
Invoke( ( MethodInvoker ) delegate {
object1.Visible = true;
object2.Visible = false;
});
}
Я основал это на записи в блоге здесь. У меня такой подход меня не подводил, поэтому я не вижу причин усложнять мой код проверкой InvokeRequired
имущество.
Надеюсь это поможет.
Создайте файл 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>
Вот улучшенная / комбинированная версия ответов Ли, Оливера и Стефана.
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;
});
Использование:
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);
}
}
}
Я бы лучше использовал один экземпляр метода 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);
}
Я хотел бы сделать это немного по-другому, я хотел бы называть себя при необходимости с действием,
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, когда закрываю свою форму, поскольку могут быть некоторые фоновые потоки, которые все еще работают...
Вы никогда не должны писать код, который выглядит так:
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 будет думать, что поток пользовательского интерфейса завершился, и завершит процесс приложения, т.е. ваше приложение завершит работу без ошибок.