2010-06-15 6 views
6

¿En qué circunstancias la actualización de un control de UI desde un subproceso no relacionado con la interfaz de usuario podría provocar que los identificadores de los procesos aumentaran continuamente al usar un delegado y .InvokeRequired?Subproceso de interfaz de usuario. Invoke() que causa la pérdida de identificadores?

Por ejemplo:

public delegate void DelegateUIUpdate(); 
private void UIUpdate() 
{ 
    if (someControl.InvokeRequired) 
    { 
     someControl.Invoke(new DelegateUIUpdate(UIUpdate)); 
     return; 
    } 
    // do something with someControl 
} 

Cuando esto se llama en un bucle o en los intervalos de temporización, las asas para el programa aumentan constantemente.

EDIT:

Si lo anterior es un comentario y modificado como tales:

public delegate void DelegateUIUpdate(); 
private void UIUpdate() 
{ 
    //if (someControl.InvokeRequired) 
    //{ 
    // someControl.Invoke(new DelegateUIUpdate(UIUpdate)); 
    // return; 
    //} 
    CheckForIllegalCrossThreadCalls = false; 
    // do something with someControl 
} 

... entonces las asas detener la incrementación, sin embargo no quiero para permitir cruz llamadas de hilo, por supuesto.

EDIT 2:

Aquí está un ejemplo que muestra las asas aumentan:

Thread thread; 
private delegate void UpdateGUI(); 
bool UpdateTheGui = false; 

public Form1() 
{ 
    InitializeComponent(); 

    thread = new Thread(new ThreadStart(MyThreadLoop)); 
    thread.Start(); 
} 

private void MyThreadLoop() 
{ 
    while (true) 
    { 
     Thread.Sleep(500); 
     if (UpdateTheGui) 
     { 
      UpdateTheGui = false; 
      UpdateTheGuiNow(); 
     } 
    } 
} 

private void UpdateTheGuiNow() 
{ 
    if (label1.InvokeRequired) 
    { 
     label1.Invoke(new UpdateGUI(UpdateTheGuiNow)); 
     return; 
    } 

    label1.Text = DateTime.Now.ToString("MM-dd-yyyy HH:mm:ss"); 
    label2.Text = DateTime.Now.ToString("MM-dd-yyyy HH:mm:ss"); 
    label3.Text = DateTime.Now.ToString("MM-dd-yyyy HH:mm:ss"); 
} 

private void btnInvoke_Click(object sender, EventArgs e) 
{ 
    UpdateTheGui = true; 
} 
+0

Tengo el mismo problema aquí, con exactamente la misma llamada en un temporizador. Gracias por mencionar CheckForIllegalCrossThreadCalls porque nunca había oído hablar de él antes. – muusbolla

Respuesta

3

El método Control.Invoke() no consume ningún control. Sin embargo, este código se llama claramente desde un hilo. Un Thread does consume identificadores, 5 de ellos.

La clase Thread no tiene un método Dispose(), aunque debería tener uno. Probablemente fue por diseño, sería muy difícil llamar de manera confiable, imposiblemente así para los hilos de subprocesos. Los 5 controles que requiere un hilo son liberados por el finalizador. Su programa requerirá cantidades cada vez mayores de identificadores si el finalizador nunca se ejecuta.

No obtener el finalizador para ejecutar es bastante inusual. Tendría que tener un programa que inicie muchos subprocesos pero no asigne mucha memoria. Esto tiende a ocurrir solo en pruebas estáticas. Puede diagnosticar esta condición con Perfmon.exe, usar los contadores de rendimiento de la memoria .NET y verificar si se están realizando colecciones gen # 0.

Si esto ocurre en un programa de producción, tendrá que llamar a GC.Collect() usted mismo para evitar una fuga de identificador fuera de control.

+0

He utilizado el patrón descrito para actualizar los componentes de la interfaz de usuario en el pasado, sin el problema de aumento de control, pero en el proyecto actual, sin duda, tiene algo que ver con esta llamada a la interfaz de usuario. Si hago un comentario sobre la parte que comprueba y llama al delegado, y en su lugar 'CheckForIllegalCrossThreadCalls = false;', los identificadores dejan de aumentar, aunque no quiero dejarlo en ese estado. Actualizaré la pregunta con esta información. – JYelton

+0

Bueno, eso desafía una explicación fácil. Danos una idea de lo que hace el resto del código y en qué afirmación específica ve aumentar el conteo de manejadores. –

+0

Se agregó una muestra que muestra el problema con más detalle. Un botón en el formulario activa la actualización. – JYelton

0

Este es el patrón estándar para el uso de Invoke a las actualizaciones del ordenar al hilo de interfaz de usuario.

¿Está seguro de que su problema no está causado por otro código en su aplicación que no está incluido en su pregunta?

+0

No del todo seguro, pero vea la edición de la pregunta y también el comentario a la respuesta de Hans. Es un problema muy inusual. – JYelton

0

No creo que esté relacionado. Tal vez solo esperando que el recolector de basura disponga los objetos recién asignados dentro de Invoke().

+0

Llamar 'GC.Collect()' en realidad detendrá el aumento de los identificadores. Curiosamente, si 'GC.Collect()' se agrega después de que el programa ha estado funcionando durante un tiempo, durante un punto de interrupción, no reducirá los controladores a un estado inicial, sino que simplemente evitará que se acumulen. – JYelton

0

De hecho veo el mismo problema que ocurre con JYelton. Tengo la misma llamada desde dentro de un hilo para actualizar la interfaz de usuario.

Tan pronto como se llama a la línea someControl.Invoke(new DelegateUIUpdate(UIUpdate));, el asa aumenta en uno. Ciertamente hay una filtración de algún tipo en la invocación, pero no tengo idea de qué lo está causando. Esto ha sido verificado en varios sistemas.

2

He visto lo mismo en mi código.Lo arreglé reemplazando Invoke con BeginInvoke. La fuga del mango desapareció.

Doron.

3

tuve el mismo problema con

this.Invoke(new DelegateClockUpdate(ChangeClock), sender, e); 

creación de un asa de cada llamada.

El asa se incrementa porque Invoke es síncrono y efectivamente el asa se dejó colgando.

O bien se debe usar un identificador de espera para procesar el resultado o el método BeginInvoke asíncrono como se muestra a continuación.

this.BeginInvoke(new DelegateClockUpdate(ChangeClock), sender, e);  
+0

¡Gracias amigo!Su respuesta es tan genial, que guardó mi aplicación de OutOfMemory y 3 días de investigaciones. Probablemente sepas que somw perfila ninjustsu ... ¡Eres un verdadero bribón! –

0

Aync call con explicit handle finalize. Exapmle:

public static class ActionExtensions 
    { 
    private static readonly ILog log = LogManager.GetLogger(typeof(ActionExtensions)); 

    /// <summary> 
    /// Async exec action. 
    /// </summary> 
    /// <param name="action">Action.</param> 
    public static void AsyncInvokeHandlers(
     this Action action) 
    { 
     if (action == null) 
     { 
     return; 
     } 

     foreach (Action handler in action.GetInvocationList()) 
     { 
     // Initiate the asychronous call. Include an AsyncCallback 
     // delegate representing the callback method, and the data 
     // needed to call EndInvoke. 
     handler.BeginInvoke(
      ar => 
      { 
      try 
      { 
       // Retrieve the delegate. 
       var handlerToFinalize = (Action)ar.AsyncState; 
       // Call EndInvoke to free resources. 
       handlerToFinalize.EndInvoke(ar); 

       var handle = ar.AsyncWaitHandle; 
       if (handle.SafeWaitHandle != null && !handle.SafeWaitHandle.IsInvalid && !handle.SafeWaitHandle.IsClosed) 
       { 
       ((IDisposable)handle).Dispose(); 
       } 
      } 
      catch (Exception exception) 
      { 
       log.Error("Async Action exec error.", exception); 
      } 
      }, 
      handler); 
     } 
    } 
    } 

Ver http://msdn.microsoft.com/en-us/library/system.iasyncresult.asyncwaithandle.aspx nota:

Cuando se utiliza el método BeginInvoke de un delegado para llamar a un método de forma asíncrona y obtener un identificador de espera de la IAsyncResult resultante, se recomienda que cierre la espera manejar tan pronto como haya terminado de usarlo, llamando al método WaitHandle.Close. Si simplemente libera todas las referencias al identificador de espera, los recursos del sistema se liberan cuando la recolección de basura recupera el identificador de espera, pero la recolección de basura funciona más eficientemente cuando los objetos desechables se cierran o eliminan explícitamente. Para obtener más información, vea la propiedad AsyncResult.AsyncWaitHandle.

0

Aquí es un método de extensión que funciona de manera similar a la llamada de invocación normal, pero va a limpiar el mango después:

namespace ExtensionMethods 
{ 
    public static class ExtensionMethods 
    { 
     public static void InvokeAndClose(this Control self, MethodInvoker func) 
     { 
      IAsyncResult result = self.BeginInvoke(func); 
      self.EndInvoke(result); 
      result.AsyncWaitHandle.Close(); 
     } 
    } 
} 

A continuación, puede llamar de manera muy similar a una invocación de la normalidad:

myForm.InvokeAndClose((MethodInvoker)delegate 
{ 
    someControl.Text = "New Value"; 
}); 

Bloqueará y esperará a que se ejecute el delegado, luego cerrará el controlador antes de volver.

Cuestiones relacionadas