2009-12-08 5 views
2

Je crée une application WPF qui appelle les services Web et affiche les données renvoyées par le service après avoir été décomposées et analysées par mon application. Le problème auquel je fais face est avec le multithreading. L'un des appels API est effectué à l'aide d'un DispatcherTimer toutes les 60 secondes. Le problème est que lorsque cet événement se déclenche, il bloque le thread d'interface utilisateur. J'ai essayé (de toutes les façons que je peux penser) pour mettre à jour l'interface utilisateur à partir du thread d'arrière-plan en utilisant les objets BackgroundWorker et Dispatcher (également délégués) et je ne peux pas comprendre cela. J'ai besoin d'un exemple montrant une étiquette sur le thread de l'interface utilisateur mis à jour par le thread d'arrière-plan. Toute aide avec ce serait fantastique car je suis sur le point de paniquer :).WPF Multithreading

J'ai regardé les autres articles et cela ne me donne pas beaucoup de sens. S'il vous plaît, portez-moi car je suis assez nouveau à ce sujet. Voici un exemple de ce que je voudrais faire. J'ai une étiquette sur la fenêtre nommée lblCase. J'appelle pullData() toutes les 60 secondes et je veux mettre à jour lblCase avec les données retournées sans bloquer l'interface utilisateur.

private void pullData() 
{ 
    //API call goes here... 
    lblCase.Content = iCase; 
} 

public MainWindow() 
{ 
    InitializeComponent(); 
    DispatcherTimer timer = new DispatcherTimer(); 
    timer.Tick += new EventHandler(timer_Tick); 
    timer.Interval = new TimeSpan(0,0,60); 
    timer.Start(); 
} 

private void timer_Tick(object sender, EventArgs e) 
{ 
    pullData(); 
} 

Répondre

4

Jetez un oeil à this question ...

acclamations,

EDIT:

Joe - pas sûr si vous obtenez plus près de groking cela, donc je pensais J'essaierais de mettre en place une utilisation simple de BackgroundWorker pour démontrer à quel point cette classe est simple et puissante!

premier - dans votre constructeur ...

public MainWindow() 
{ 
    InitializeComponent(); 

    BackgroundWork worker = new BackgroundWorker(); 
    worker = new BackgroundWorker(); 
    worker.DoWork += new DoWorkEventHandler(worker_DoWork); 
    worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted); 

    System.Timers.Timer t = new System.Timers.Timer(10000); // 10 second intervals 
    t.Elapsed += (sender, e) => 
    { 
     // Don't try to start the work if it's still busy with the previous run... 
     if (!worker.IsBusy) 
      worker.RunWorkerAsync(); }; 
    } 
} 

donc nous avons mis en place quelque chose à déléguer certains travaux (dans la méthode « worker_DoWork ») sur un fil de fond ... quelle que soit happends dans cette méthode sera pas d'impact sur le fil de l'interface utilisateur, et il devrait ressembler à:

private void worker_DoWork(object sender, DoWorkEventArgs e) 
{ 
    // Whatever comes back from the lengthy process, we can put into e.Result 
    e.Result = DoMyBigOperation(); 
} 

maintenant, quand ce fil est terminé, il déclenche l'événement RunWorkerCompleted, que nous avons traité comme tel:

private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 
{ 
    // First, handle the case where an exception was thrown. 
    if (e.Error != null) 
    { 
    // handle the System.Exception 
     MessageBox.Show(e.Error.Message); 
    } 
    else if (e.Cancelled) 
    { 
     // now handle the case where the operation was cancelled... 
     lblCase.Content = "The operation was cancelled"; 
    } 
    else 
    { 
     // Finally, handle the case where the operation succeeded 
     lblCase.Content = e.Result.ToString(); 
    } 
} 

Espérons que cela aide! IanR

+0

Un autre échantillon (la mienne) qui fait bien la même chose: http://stackoverflow.com/questions/ 1794402/asynchronous-waiting-while-c-function-is-execution/1794947 # 1794947 Le code de Ian est un peu mieux cependant ;-) Le seul commentaire que je ferais est que l'utilisation de la capture de variables dans les délégués anonymes peut être un peu d'esprit penché quand vous le voyez pour la première fois. Pour rendre le code un peu plus évident, je ferais de "worker" un membre de la classe MainWindow. – donovan

+0

bon point, donovan et merci pour les gentils mots :) – kiwipom

+0

Merci pour les réponses, messieurs. Je vais commencer à tester ces suggestions sous peu et vous ferai savoir ce qui fonctionne pour moi. – Joe

1

Vous ne pouvez mettre à jour un contrôle qu'à partir du thread qui a créé le contrôle. Cela dit, il suffit d'utiliser un objet worker d'arrière-plan pour obtenir les données et une fois ce travail terminé, mettez à jour le contrôle à l'aide du thread d'interface utilisateur.

+0

Mis à jour ma question ... – Joe

0

Utilisez BeginInvoke() pour mettre à jour le thread d'interface utilisateur à partir d'un autre thread. Voici un article décrivant comment l'utiliser.

Éditer: J'ai essayé le programme suivant basé sur le code que vous avez donné dans votre question et il met à jour l'interface utilisateur bien (parce que tout se passe sur le fil de l'interface utilisateur).

using System; 
using System.Threading; 
using System.Windows.Threading; 

namespace BeginInvoke 
{ 
    public partial class Window1 
    { 
     public Window1() 
     { 
      InitializeComponent(); 
      x_tid.Text = Thread.CurrentThread.ManagedThreadId.ToString(); 

      DispatcherTimer timer = new DispatcherTimer(); 
      timer.Tick += timer_Tick; 
      timer.Interval = new TimeSpan(0, 0, 1); 
      timer.Start(); 
     } 

     private void timer_Tick(object sender, EventArgs e) 
     { 
      Thread.SpinWait(900); 
      x_text.Text = DateTime.Now.ToString(); 
      x_tid.Text = Thread.CurrentThread.ManagedThreadId.ToString(); 
     } 
    } 
} 

XAML est:

<Window x:Class="BeginInvoke.Window1" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Title="Window1" Height="300" Width="300"> 
    <StackPanel> 
     <TextBlock x:Name="x_text" /> 
     <TextBlock x:Name="x_tid" /> 
    </StackPanel> 
</Window> 
+0

J'ai lu cet article et je ne sais toujours pas très bien comment tout cela fonctionne. Pourriez-vous poster un exemple pour accompagner l'extrait que j'ai posté? – Joe

0

Pour élaborer ma réponse originale sur l'utilisation BeginInvoke de mettre à jour l'interface utilisateur à partir d'un fil non d'interface utilisateur, voici un exemple de travail. BeginInvoke fonctionne hors du répartiteur. Si vous utilisez Silverlight, vous pouvez utiliser BeginInvoke à partir de System.Windows.Deployment.Current.Dispatcher.BeginInvoke().

using System; 
using System.Threading; 
using System.Windows.Threading; 

namespace BeginInvoke2 
{ 
    public partial class Window1 
    { 
     public Window1() 
     { 
      InitializeComponent(); 
      ThreadPool.QueueUserWorkItem(Proc, Dispatcher); 
     } 

     private void Proc(object state) 
     { 
      var disp = (Dispatcher) state; 
      var tid = Thread.CurrentThread.ManagedThreadId.ToString(); 

      // Use BeginInvoke to do the operations on the UI thread 
      disp.BeginInvoke((Action)(() => 
      { 
       x_tid1.Text = "threadpool thread: " + Thread.CurrentThread.ManagedThreadId.ToString(); 
       x_tid2.Text = "  ui thread: " + tid; 
      })); 

      // Can't do the following operations because we are not in the UI 
      // thread 
      //x_text.Text = "In Proc"; 
      //x_tid.Text = Thread.CurrentThread.ManagedThreadId.ToString(); 
     } 
    } 
} 

fichier XAML

<Window x:Class="BeginInvoke2.Window1" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Title="Window1" Height="300" Width="300"> 
    <StackPanel> 
     <TextBlock x:Name="x_tid1" /> 
     <TextBlock x:Name="x_tid2" /> 
    </StackPanel> 
</Window> 
1

Je préfère créer une classe de travailleur, le passer, un délégué de mise à jour, et de lancer la méthode DoSomething du travailleur dans un thread séparé. Chaque fois que ce thread doit mettre à jour l'interface utilisateur, il rappelle le délégué de mise à jour, qui effectue la mise à jour via le répartiteur du contrôle. Dans cet exemple, je passe aussi le contrôle que j'ai plusieurs threads chaque mise à jour de leur propre TextBlock:

private void UpdateTextBlock(TextBlock textBlockArg, string textArg) 
    { 
     textBlockArg.Dispatcher.Invoke(
      System.Windows.Threading.DispatcherPriority.Normal 
      , new System.Windows.Threading.DispatcherOperationCallback(delegate 
      { 
       textBlockArg.Text = textArg; 
       return null; 
      }), null); 
    }