2009-10-29 7 views
5

¿Qué soluciones tengo si deseo evitar que la UI se congele mientras deserializo una gran cantidad de elementos de UI en WPF? Me llegan errores al quejarse de que los objetos pertenecen a la UI Thread cuando intento cargarlos en otro hilo. Entonces, ¿qué opciones tengo para evitar el error de Vista "Programa que no responde" mientras estoy cargando mis datos de UI? ¿Puedo confiar en una solución de subproceso único o me falta algo con respecto a tal vez múltiples subprocesos de UI?Evite que la IU se congele sin hilos adicionales

+3

En Windows Forms puede usar Application.DoEvents(). No sé si funciona en WPF. – Qwertie

Respuesta

11

Si solo usa un único hilo, la IU se congelará mientras realiza cualquier procesamiento.

Si utiliza un subproceso BackgroundWorker tendrá más control sobre lo que ocurre & cuando.

Para actualizar la interfaz de usuario necesita usar Dispatcher.Invoke desde el hilo de fondo para ordenar la llamada a través del límite del hilo.

Dispatcher.Invoke(DispatcherPriority.Background, 
        new Action(() => this.TextBlock.Text = "Processing"); 
0

fijas que se pueden hacer de su procesamiento largo en un hilo separado, pero cuando haya terminado usted tiene que sincronizar con el hilo de interfaz de usuario llamando Dispatcher.BeginInvoke(your_UI_action_here)

0

Recomendaciones del blog OldNewThing.

Lo mejor es ir por la ruta de subprocesos, tener un subproceso de GUI y generar la carga de trabajo en otro subproceso que, cuando finalice los informes en el subproceso de la GUI principal, se complete. La razón de esto es porque no entrarás en problemas con la interfaz GUI.

So One GUI Thread Muchos hilos de trabajo que hacen el trabajo.

Si alguno de sus subprocesos se cuelga, el usuario está en control directo sobre su aplicación, puede cerrar el hilo sin afectar su experiencia con la interfaz de la aplicación. Esto lo hará feliz porque su usuario se sentirá en control además de él constantemente, haga clic en el BOTÓN DETENER Y NO DEJE DE BÚSQUEDA.

0

Pruebe freezing su UIElements. Los objetos congelados se pueden pasar entre subprocesos sin encontrar una InvalidOperationException, por lo que los deserializará & congélelos en un subproceso de fondo antes de usarlos en su subproceso de interfaz de usuario.

Como alternativa, considere enviar de nuevo las deserializaciones individuales al subproceso UI con prioridad de fondo. Esto no es óptimo, ya que el hilo de la interfaz de usuario todavía tiene que hacer todo el trabajo para deserializar estos objetos y se agregan algunos gastos indirectos al enviarlos como tareas individuales, pero al menos no se bloqueará la interfaz de usuario: eventos de mayor prioridad como entrada podrá intercalarse con su trabajo de deserialización de menor prioridad.

+1

Esto solo funciona para freezables y los elementos de UI no son freezables. Entonces, si solo está hablando de crear geometría, pinceles, etc., esto funciona, pero si está hablando de construir elementos de interfaz de usuario (es decir, clase visual y hasta la cadena de herencia) no funciona. –

+0

Vaya, buen punto en UIElement no ser freezable. –

2

Here is a wonderful blog posting from Dwane Need que analiza todas las opciones disponibles para trabajar con elementos de IU entre múltiples hilos.

Realmente no has dado suficientes detalles para dar una buena receta. Por ejemplo, ¿por qué está creando elementos UI usted mismo en vez de usar databinding? Puede que tenga una buena razón, pero sin más detalles es difícil dar un buen consejo. Como otro ejemplo de detalle que sería útil, ¿busca construir jerarquías complejas de control profundamente anidadas para cada dato o simplemente necesita dibujar una forma simple?

2

Puede girar el flujo de control sobre su cabeza usando DispatcherFrames, permitiendo que la deserialización proceda en el hilo de la interfaz de usuario en el fondo.

Primero necesita una forma de obtener control periódicamente durante la deserialización. No importa qué deserializador esté utilizando, tendrá que invocar conjuntos de propiedades en sus objetos, por lo que normalmente puede agregar código a los establecedores de propiedades. Alternativamente, puedes modificar el deserializador. En cualquier caso, asegúrese de que su código se llama la frecuencia suficiente

Cada vez que reciba el control, todo lo que tiene que hacer es:

  1. Crear un DispatcherFrame
  2. cola de un evento para el despachador usando BeginInvoke que Continuar conjuntos = false en el marco de
  3. uso PushFrame para iniciar el bastidor se ejecuta en el Dispatcher

Además, cuando se llama a sí mismo el deserializer asegúrese años u hacer desde Dispatcher.BeginInvoke, o que su código de llamada no se mantiene ningún bloqueo etc.

es como se vería aquí:

public partial class MyWindow 
    { 
    SomeDeserializer _deserializer = new SomeDeserializer(); 

    byte[] _sourceData; 
    object _deserializedObject; 

    ... 

    void LoadButton_Click(...) 
    { 
     Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() => 
     { 
     _deserializedObject = _deserializer.DeserializeObject(_sourceData); 
     })); 
    } 
    } 

    public class OneOfTheObjectsBeingDeserializedFrequently 
    { 
    ... 

    public string SomePropertyThatIsFrequentlySet 
    { 
     get { ... } 
     set { ...; BackgroundThreadingSolution.DoEvents(); } 
    } 
    } 

    public class BackgroundThreadingSolution 
    { 
    [ThreadLocal] 
    static DateTime _nextDispatchTime; 

    public static void DoEvents() 
    { 
     // Limit dispatcher queue running to once every 200ms 
     var now = DateTime.Now; 
     if(now < _nextDispatchTime) return; 
     _nextDispatchTime = now.AddMilliseconds(200); 

     // Run the dispatcher for everything over background priority 
     var frame = new DispatcherFrame(); 
     Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() => 
     { 
     frame.Continue = false; 
     })); 
     Dispatcher.PushFrame(frame); 
    } 
    } 

Comprobación DateTime.Now en DoEvents() no es en realidad requerido para que esta técnica funcione, pero mejorará el rendimiento si SomeProperty se configura con mucha frecuencia durante la deserialización.

Edit: Inmediatamente después de que escribí esto, me di cuenta de que hay una manera más fácil de implementar el método DoEvents. En lugar de utilizar DispatcherFrame, basta con utilizar Dispatcher.Invoke con una acción vacía:

public static void DoEvents() 
    { 
     // Limit dispatcher queue running to once every 200ms 
     var now = DateTime.Now; 
     if(now < _nextDispatchTime) return; 
     _nextDispatchTime = now.AddMilliseconds(200); 

     // Run the dispatcher for everything over background priority 
     Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Background, new Action(() => {})); 
    } 
1

He tenido un problema similar con mi panel que se movía sus artículos. La IU estaba congelada porque estaba usando un DispatcherTimer en la prioridad Cargado. El problema desapareció tan pronto como lo cambié a DispatcherPriority.Input.

Cuestiones relacionadas