Автоматизация кода кода InvokeRequired
Мне стало известно, насколько часто нужно писать следующий код в управляемом событиями графическом интерфейсе, где
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;
}
}
Это неудобный шаблон в С#, как для запоминания, так и для ввода. Кто-нибудь придумал какой-то ярлык или конструкцию, которая автоматизирует это до определенной степени? Было бы здорово, если бы был способ привязать функцию к объектам, выполняющим эту проверку, без необходимости выполнять всю эту дополнительную работу, например, ярлык типа object1.InvokeIfNecessary.visible = true
.
Предыдущие ответы обсуждали нецелесообразность просто вызова Invoke() каждый раз, и даже тогда синтаксис Invoke() неэффективен и все еще неудобен для решения.
Итак, кто-нибудь понял какие-нибудь ярлыки?
Ответы
Ответ 1
Ли подход можно упростить далее
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);
});
Нет необходимости передавать элемент управления в качестве параметра для делегата. С# автоматически создает closure.
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 ниже для беспокойства по поводу этого предложения.
Ответ 2
Вы можете написать метод расширения:
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
Ответ 3
Здесь форма, которую я использовал во всем моем коде.
private void DoGUISwitch()
{
Invoke( ( MethodInvoker ) delegate {
object1.Visible = true;
object2.Visible = false;
});
}
Я основывал это на записи в блоге здесь. У меня не было такого подхода, и я не вижу причины усложнять мой код проверкой свойства InvokeRequired
.
Надеюсь, что это поможет.
Ответ 4
Создайте файл 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>
Ответ 5
Здесь представлена улучшенная/комбинированная версия Ли, Оливера и Стефана.
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;
});
Ответ 6
Я предпочел бы использовать единственный экземпляр метода Delegate вместо создания нового экземпляра каждый раз.
В моем случае я использовал для отображения сообщений о прогрессе и (info/error) от копирования 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);
}
Ответ 7
Применение:
control.InvokeIfRequired(c => c.Visible = false);
return control.InvokeIfRequired(c => {
c.Visible = value
return c.Visible;
});
Код:
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 =>
obj.InvokeRequired ? (TOut)obj.Invoke(func, new object[] { obj }) : func(obj);
}
Ответ 8
Мне нравится делать это немного по-другому, мне нравится называть "себя", если нужно, с помощью Action,
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, когда закрываю форму, поскольку могут быть некоторые фоновые потоки, которые все еще работают...
Ответ 9
Вы никогда не должны писать код, который выглядит так:
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, будет думать, что поток пользовательского интерфейса закончился и убьет процесс приложения, то есть ваше приложение выйдет без ошибок.