2009-10-29 5 views
5

Quelles solutions ai-je pour empêcher l'IU de geler pendant que je désérialise un grand nombre d'éléments d'interface utilisateur dans WPF? Je reçois des erreurs se plaignant que les objets appartiennent sur le Thread UI lorsque je suis en train de les charger dans un autre thread. Alors, quelles options ai-je pour empêcher l'erreur Vista "Ne répond pas au programme" pendant que je charge mes données d'interface utilisateur? Puis-je me fier à une solution à un seul thread ou est-ce que je manque quelque chose concernant peut-être plusieurs threads de l'interface utilisateur?Empêche le gel de l'interface utilisateur sans threads supplémentaires

+3

Dans Windows Forms, vous pouvez utiliser Application.DoEvents(). Je ne sais pas si cela fonctionne dans WPF. – Qwertie

Répondre

11

Si vous n'utilisez qu'un seul thread, l'interface utilisateur se bloquera pendant toute la durée du traitement.

Si vous utilisez un thread BackgroundWorker, vous aurez plus de contrôle sur ce qui se passe & quand.

Pour mettre à jour l'interface utilisateur, vous devez utiliser Dispatcher.Invoke à partir de votre thread d'arrière-plan pour marshal l'appel à travers la limite de fil.

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

Vous pouvez toujours faire votre traitement long dans un thread séparé, mais lorsque vous avez terminé devez synchroniser avec le thread d'interface utilisateur en appelant Dispatcher.BeginInvoke(your_UI_action_here)

0

Recommandations du blog OldNewThing.

Il est préférable que vous utilisiez la procédure filetée, que vous disposiez d'un thread graphique et que vous chargiez votre travail dans un autre thread qui, une fois terminé, renvoie le thread principal de l'interface graphique. La raison en est que vous n'entrerez pas dans les problèmes de thread avec votre interface graphique.

So Un fil GUI Nombreux threads de travail qui font le travail.

Si l'un de vos threads se bloque, l'utilisateur est en contrôle direct sur votre application peut fermer le thread sans affecter son expérience avec l'interface de l'application. Cela le rendra heureux parce que votre utilisateur se sentira dans le contrôle autre que lui cliquez constamment sur ce bouton d'arrêt et il ne va pas arrêter de chercher.

0

Essayez freezing vos UIElements. Les objets gelés peuvent être transmis entre les threads sans rencontrer d'exception InvalidOperationException, donc vous les désérialisez & les geler sur un thread d'arrière-plan avant de les utiliser sur votre thread d'interface utilisateur.

Vous pouvez également envisager de renvoyer les désérialisations individuelles vers le thread UI à la priorité d'arrière-plan. Ce n'est pas optimal, car le thread de l'interface utilisateur doit encore faire tout le travail pour désérialiser ces objets et il y a une surcharge ajoutée en les répartissant comme des tâches individuelles, mais au moins vous ne bloquerez pas l'interface utilisateur. sera en mesure d'être intercalé avec votre travail de désérialisation de moindre priorité.

+1

Cela ne fonctionne que pour les freezables et les éléments de l'interface utilisateur ne sont pas freezable. Donc, si vous parlez de créer une géométrie, des pinceaux, etc., cela fonctionne, mais si vous parlez de construire des éléments d'interface utilisateur (c'est-à-dire la classe Visual et la chaîne d'héritage), ce n'est pas le cas. –

+0

Oups, bon point sur UIElement n'étant pas gelable. –

2

Here is a wonderful blog posting from Dwane Need qui décrit toutes les options disponibles pour travailler avec des éléments d'interface utilisateur parmi plusieurs threads.

Vous n'avez vraiment pas donné assez de détails pour donner une bonne prescription. Par exemple, pourquoi créez-vous vous-même des éléments d'interface utilisateur au lieu d'utiliser la liaison de données? Vous pourriez avoir une bonne raison, mais sans plus de détails, il est difficile de donner de bons conseils. Comme autre exemple de détail utile, cherchez-vous à construire des hiérarchies de contrôle complexes et imbriquées pour chaque donnée ou avez-vous besoin de dessiner une forme simple?

2

Vous pouvez activer le flux de contrôle à l'aide de DispatcherFrames, ce qui permet de désérialiser le thread d'interface en arrière-plan.

D'abord vous avez besoin d'un moyen d'obtenir le contrôle périodiquement pendant la désérialisation. Quel que soit le désérialiseur que vous utilisez, il devra appeler des ensembles de propriétés sur vos objets, de sorte que vous pouvez généralement ajouter du code aux paramètres de propriété. Vous pouvez également modifier le désérialiseur. Dans tous les cas, assurez-vous que votre code est appelé assez souvent

Chaque fois que vous contrôlez recevez, tout ce que vous devez faire est:

  1. Créer un DispatcherFrame
  2. file d'attente d'un événement au répartiteur à l'aide BeginInvoke que jeux Continuer = false sur le cadre
  3. Utilisez PushFrame pour démarrer le cadre en cours d'exécution sur le répartiteur

En outre, lorsque vous appelez le désérialiseur lui-même assurez-vous yo u de le faire Dispatcher.BeginInvoke, ou que votre code d'appel ne détient pas de serrures etc.

Voici à quoi il ressemblerait:

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); 
    } 
    } 

Vérification DateTime.Now dans DoEvents() est pas vraiment requis pour que cette technique fonctionne, mais améliorera les performances si SomeProperty est défini très fréquemment lors de la désérialisation. Juste après avoir écrit ceci, j'ai réalisé qu'il existe un moyen plus simple d'implémenter la méthode DoEvents. Au lieu d'utiliser DispatcherFrame, il suffit d'utiliser Dispatcher.Invoke avec une action vide:

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

J'ai eu un problème similaire avec mon panneau qui se déplaçait ses éléments. L'interface utilisateur gelait parce que j'utilisais un DispatcherTimer à la priorité Loaded. Le problème est parti dès que je l'ai changé en DispatcherPriority.Input.

Questions connexes