Automatización del patrón de código InvokeRequired
Me he vuelto dolorosamente consciente de la frecuencia con la que es necesario escribir el siguiente patrón de código en código GUI controlado por eventos, donde
private void DoGUISwitch() {
// cruisin for a bruisin' through exception city
object1.Visible = true;
object2.Visible = false;
}
se convierte en:
private void DoGUISwitch() {
if (object1.InvokeRequired) {
object1.Invoke(new MethodInvoker(() => { DoGUISwitch(); }));
} else {
object1.Visible = true;
object2.Visible = false;
}
}
Este es un patrón incómodo en C#, tanto para recordar como para escribir. ¿A alguien se le ha ocurrido algún tipo de atajo o construcción que automatice esto hasta cierto punto? Sería genial si hubiera una manera de adjuntar una función a los objetos que realice esta verificación sin tener que realizar todo este trabajo adicional, como un object1.InvokeIfNecessary.visible = true
atajo de tipo.
Las respuestas anteriores han discutido la impracticabilidad de simplemente llamar a Invoke() cada vez, e incluso entonces la sintaxis de Invoke() es ineficiente y aún incómoda de manejar.
Entonces, ¿alguien ha descubierto algún atajo?
El enfoque de Lee se puede simplificar aún más.
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();
}
}
Y se puede llamar así
richEditControl1.InvokeIfRequired(() =>
{
// Do anything you want with the control here
richEditControl1.RtfText = value;
RtfHelpers.AddMissingStyles(richEditControl1);
});
No es necesario pasar el control como parámetro al delegado. C# crea automáticamente un cierre .
Si debe devolver un valor, puede utilizar esta implementación:
private static T InvokeIfRequiredReturn<T>(this Control control, Func<T> function)
{
if (control.InvokeRequired) {
return (T)control.Invoke(function);
} else {
return function();
}
}
ACTUALIZAR :
Según varios otros carteles Control
, se puede generalizar como ISynchronizeInvoke
:
public static void InvokeIfRequired(this ISynchronizeInvoke obj,
MethodInvoker action)
{
if (obj.InvokeRequired) {
obj.Invoke(action, null);
} else {
action();
}
}
DonBoitnott señaló que, a diferencia Control
de la ISynchronizeInvoke
interfaz, se requiere una matriz de objetos para el Invoke
método como lista de parámetros para el archivo action
.
De acuerdo con la documentación del método ISynchronizeInvoke.Invoke(Delegate, Object[]) podemos pasar null
si no se necesitan argumentos.
ACTUALIZACIÓN 2
Ediciones sugeridas por Mike de Klerk (consulte el comentario en el primer fragmento de código para ver el punto de inserción):
// 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);
}
Consulte los comentarios de ToolmakerSteve y nawfal a continuación si tiene dudas sobre esta sugerencia.
Podrías escribir un método de extensión:
public static void InvokeIfRequired(this Control c, Action<Control> action)
{
if(c.InvokeRequired)
{
c.Invoke(new Action(() => action(c)));
}
else
{
action(c);
}
}
Y úsalo así:
object1.InvokeIfRequired(c => { c.Visible = true; });
EDITAR: Como señala Simpzon en los comentarios, también puede cambiar la firma a:
public static void InvokeIfRequired<T>(this T c, Action<T> action)
where T : Control
Este es el formulario que he estado usando en todo mi código.
private void DoGUISwitch()
{
Invoke( ( MethodInvoker ) delegate {
object1.Visible = true;
object2.Visible = false;
});
}
Me he basado en la entrada del blog aquí . Este enfoque no me ha fallado, por lo que no veo ninguna razón para complicar mi código con una verificación de la InvokeRequired
propiedad.
Espero que esto ayude.
Cree un archivo ThreadSafeInvoke.snippet y luego puede seleccionar las declaraciones de actualización, hacer clic derecho y seleccionar 'Rodear con...' o 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>
Aquí hay una versión mejorada/combinada de las respuestas de Lee, Oliver y Stephan.
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);
}
}
La plantilla permite un código flexible y sin conversión que es mucho más legible, mientras que el delegado dedicado proporciona eficiencia.
progressBar1.InvokeIfRequired(o =>
{
o.Style = ProgressBarStyle.Marquee;
o.MarqueeAnimationSpeed = 40;
});