extensions - extension methods c#
¿Es apropiado ampliar el control para proporcionar una funcionalidad Invoke/BeginInvoke constantemente segura? (3)
Esto no es en realidad una respuesta, pero responde algunos comentarios de la respuesta aceptada.
Para los patrones estándar IAsyncResult
, el método BeginXXX
contiene el parámetro AsyncCallback
, por lo que si quiere decir "No me importa esto, simplemente llame a EndInvoke cuando haya terminado e ignore el resultado", puede hacer algo como esto (esto es para Action
pero debe poder ajustarse para otros tipos de delegados):
...
public static void BeginInvokeEx(this Action a){
a.BeginInvoke(a.EndInvoke, a);
}
...
// Don''t worry about EndInvoke
// it will be called when finish
new Action(() => {}).BeginInvokeEx();
(Desafortunadamente no tengo una solución para no tener una función de ayuda sin declarar una variable cada vez que utilizo este patrón).
Pero para Control.BeginInvoke
no tenemos AsyncCallBack
, por lo que no hay una manera fácil de expresar esto con Control.EndInvoke
garantizado para ser llamado. La forma en que se diseñó indica que Control.EndInvoke
es opcional.
Durante el mantenimiento de una aplicación anterior que violó gravemente las reglas de actualización de hilos cruzados en winforms, creé el siguiente método de extensión para solucionar rápidamente las llamadas ilegales cuando las descubrí:
/// <summary>
/// Execute a method on the control''s owning thread.
/// </summary>
/// <param name="uiElement">The control that is being updated.</param>
/// <param name="updater">The method that updates uiElement.</param>
/// <param name="forceSynchronous">True to force synchronous execution of
/// updater. False to allow asynchronous execution if the call is marshalled
/// from a non-GUI thread. If the method is called on the GUI thread,
/// execution is always synchronous.</param>
public static void SafeInvoke(this Control uiElement, Action updater, bool forceSynchronous)
{
if (uiElement == null)
{
throw new ArgumentNullException("uiElement");
}
if (uiElement.InvokeRequired)
{
if (forceSynchronous)
{
uiElement.Invoke((Action)delegate { SafeInvoke(uiElement, updater, forceSynchronous); });
}
else
{
uiElement.BeginInvoke((Action)delegate { SafeInvoke(uiElement, updater, forceSynchronous); });
}
}
else
{
if (!uiElement.IsHandleCreated)
{
// Do nothing if the handle isn''t created already. The user''s responsible
// for ensuring that the handle they give us exists.
return;
}
if (uiElement.IsDisposed)
{
throw new ObjectDisposedException("Control is already disposed.");
}
updater();
}
}
Uso de muestra:
this.lblTimeDisplay.SafeInvoke(() => this.lblTimeDisplay.Text = this.task.Duration.ToString(), false);
Me gusta cómo puedo aprovechar los cierres para leer, aunque forzosamente síntoma debe ser cierto en ese caso:
string taskName = string.Empty;
this.txtTaskName.SafeInvoke(() => taskName = this.txtTaskName.Text, true);
No cuestiono la utilidad de este método para arreglar llamadas ilegales en código heredado, pero ¿qué pasa con el nuevo código?
¿Está bien el diseño para usar este método para actualizar la interfaz de usuario en una pieza de software nuevo cuando no se sabe qué hilo está intentando actualizar la interfaz de usuario, o el nuevo código de Winforms generalmente contiene un método dedicado específico con la Invoke()
apropiada Invoke()
- plomería relacionada para todas las actualizaciones de UI? (Trataré de utilizar primero las otras técnicas apropiadas de procesamiento en segundo plano, por ejemplo, por ejemplo, BackgroundWorker).
Curiosamente, esto no funcionará para ToolStripItems . Recientemente descubrí que derivan directamente de Component lugar de desde Control . En su lugar, se debe usar la invocación de ToolStrip
contiene.
Seguimiento a los comentarios:
Algunos comentarios sugieren que:
if (uiElement.InvokeRequired)
debiera ser:
if (uiElement.InvokeRequired && uiElement.IsHandleCreated)
Considere la siguiente documentación msdn :
Esto significa que InvokeRequired puede devolver false si no se requiere Invoke (la llamada se produce en el mismo subproceso), o si el control se creó en un subproceso diferente pero el asa del control aún no se ha creado.
En el caso donde el mango del control aún no se haya creado, no debería simplemente llamar propiedades, métodos o eventos en el control. Esto podría hacer que el control se cree en el hilo de fondo, aislando el control en un hilo sin una bomba de mensajes y haciendo que la aplicación sea inestable.
Puede protegerse contra este caso también verificando el valor de IsHandleCreated cuando InvokeRequired devuelve falso en una cadena de fondo.
Si el control se creó en un hilo diferente pero el mango del control aún no se ha creado, InvokeRequired
devuelve falso. Esto significa que si InvokeRequired
devuelve true
, IsHandleCreated
siempre será verdadero. Volver a probarlo es redundante e incorrecto.
Me gusta la idea general, pero sí veo un problema. Es importante procesar EndInvokes, o puede tener pérdidas de recursos. Sé que mucha gente no cree en esto, pero realmente es verdad.
Aquí hay un enlace hablando de eso . Hay otros también.
Pero la respuesta principal que tengo es: Sí, creo que tienes una buena idea aquí.
También debe crear métodos de extensión Begin y End. Y si usa medicamentos genéricos, puede hacer que la llamada se vea un poco mejor.
public static class ControlExtensions
{
public static void InvokeEx<T>(this T @this, Action<T> action)
where T : Control
{
if (@this.InvokeRequired)
{
@this.Invoke(action, new object[] { @this });
}
else
{
if ([email protected])
return;
if (@this.IsDisposed)
throw new ObjectDisposedException("@this is disposed.");
action(@this);
}
}
public static IAsyncResult BeginInvokeEx<T>(this T @this, Action<T> action)
where T : Control
{
return @this.BeginInvoke((Action)delegate { @this.InvokeEx(action); });
}
public static void EndInvokeEx<T>(this T @this, IAsyncResult result)
where T : Control
{
@this.EndInvoke(result);
}
}
Ahora tus llamadas son un poco más cortas y limpias:
this.lblTimeDisplay.InvokeEx(l => l.Text = this.task.Duration.ToString());
var result = this.BeginInvokeEx(f => f.Text = "Different Title");
// ... wait
this.EndInvokeEx(result);
Y con respecto a Component
s, solo invoque en el formulario o contenedor en sí.
this.InvokeEx(f => f.toolStripItem1.Text = "Hello World");