2010-06-04 5 views
5

J'ai un thread qui traite un travail analytique.C# Problème de thread à l'aide d'Invoke à partir d'un thread d'arrière-plan

private static void ThreadProc(object obj) 
    { 
     var grid = (DataGridView)obj; 
     foreach (DataGridViewRow row in grid.Rows) 
     { 
      if (Parser.GetPreparationByClientNameForSynonims(row.Cells["Prep"].Value.ToString()) != null) 
       UpdateGridSafe(grid,row.Index,1); 
      Thread.Sleep(10); 
     } 
    } 

Je veux mettre à jour en toute sécurité ma GRIDVIEW à boucle, donc j'utilise manière classique:

private delegate void UpdateGridDelegate(DataGridView grid, int rowIdx, int type); 
    public static void UpdateGridSafe(DataGridView grid, int rowIdx, int type) 
    { 
     if (grid.InvokeRequired) 
     { 
      grid.Invoke(new UpdateGridDelegate(UpdateGridSafe), new object[] { grid, rowIdx, type }); 
     } 
     else 
     { 
      if (type == 1) 
       grid.Rows[rowIdx].Cells["Prep"].Style.ForeColor = Color.Red; 
      if (type==2) 
       grid.Rows[rowIdx].Cells["Prep"].Style.ForeColor = Color.ForestGreen; 

     } 
    } 

Mais quand je rentre UpdateGridSafe, le programme se bloque.

Dans le débogueur, je vois que grid.Invoke n'appelle pas UpdateGridSafe. S'il vous plaît, aidez - quel est le problème?

EDIT

fil classique créer un code

 Thread t = new Thread(new ParameterizedThreadStart(ThreadProc)); 
     t.Start(dgvSource); 
     t.Join(); 
     MessageBox.Show("Done", "Info"); 
+0

Pourquoi utilisez-vous thread.sleep? Pouvez-vous utiliser la fonction de rappel ici, si vous voulez effectuer une opération après la mise à jour de la grille? – Ram

Répondre

6

Vous avez un blocage. Votre t.Join bloque le thread graphique jusqu'à ce que ThreadProc soit terminé. ThreadProc est bloqué en attente de t.Join pour terminer afin qu'il puisse faire les appels.

Bad code

Thread t = new Thread(new ParameterizedThreadStart(ThreadProc)); 
    t.Start(dgvSource); 
    t.Join(); <--- DEADLOCK YOUR PROGRAM 
    MessageBox.Show("Done", "Info"); 

Bon Code

backgroundWorker1.RunWorkerAsync 

    private void backgroundWorker1_DoWork(object sender, 
     DoWorkEventArgs e) 
    {  
     var grid = (DataGridView)obj;  
     foreach (DataGridViewRow row in grid.Rows)  
     {  
      if (Parser.GetPreparationByClientNameForSynonims(row.Cells["Prep"].Value.ToString()) != null)  
       UpdateGridSafe(grid,row.Index,1);  
      // don't need this Thread.Sleep(10);  
     }  
    } 

    private void backgroundWorker1_RunWorkerCompleted(
      object sender, RunWorkerCompletedEventArgs e) 
     { 
     MessageBox.Show("Done", "Info"); 
} 

EDIT

utiliser également BeginInvoke au lieu de Invoke. De cette façon, votre thread de travail n'a pas à bloquer chaque fois que vous mettez à jour l'interface graphique.

Référence

Avoid Invoke(), prefer BeginInvoke()

1

La déclaration Invoke attendra jusqu'à ce que la pompe principale du message de fil n'est pas occupé, et peut gérer un nouveau message. Si votre thread principal est occupé, l'invocation sera bloquée.

Dans votre cas, il semble que votre code supérieur fonctionne dans une boucle serrée, donc il n'y a jamais de chance que l'invocation dans le code du bas fonctionne réellement. Si vous changez le Thread.Sleep dans votre bloc de code supérieur en quelque chose avec un temps dedans, j'espère que cela donnera à votre thread principal une chance de gérer l'appel .Invoke. En fonction de ce que votre thread d'application principal est en train de faire, vous devrez peut-être finir votre première boucle avant que l'un des appels .Invoke ne s'exécute - si c'est le cas, je peux poster du code modifié qui fonctionnera mieux.

+0

Je passe à Thread.Sleep (10); Mais la situation est la même :(Le fil de l'application principale est le fil de l'interface utilisateur, ne fonctionne pas, veuillez écrire un code qui pourrait mieux fonctionner –

+0

@Andrew - si le fil principal dans le premier bloc de code est votre fil d'interface utilisateur, Si vous appelez ThreadProc() sur un thread différent, alors il ne répondra pas aux requêtes .Invoke jusqu'à ce que le traitement de chaque ligne soit terminé dans le forEach que vous exécutez. La pompe de message devrait être disponible - le fait que le .Invoke soit suspendu indique que vous bloquez votre thread principal de l'interface utilisateur. Déterminez où votre thread principal est bloqué et résolvez cela, et vos appels de .Invoke recommencent à fonctionner – SqlRyan

+0

Je crée ThreadProc du thrad principal, pour traiter un peu de travail à l'arrière-plan –

1

Jamais, jamais, chaque utilisation Thread.Sleep (0). Il ne fait pas ce que vous pensez et ne vous causera que de la douleur. Par exemple, dans une boucle serrée, le système d'exploitation peut décider que le thread qui vient de dormir est le suivant à exécuter. En conséquence, vous ne céderez pas réellement le fil.

Essayez à nouveau votre code en utilisant Thread.Sleep (1) toutes les N itérations où N représente environ 0,25 à 1,0 seconde de travail.

Si cela ne fonctionne pas faites le moi savoir et nous pouvons voir comment ThreadProc est créé.

Références

Never Sleep(0) in an Infinite Loop

EDIT

Argument pour ne jamais utiliser Thread.Sleep

Thread.Sleep is a sign of a poorly designed program.

+0

Merci pour la réponse. J'essaie de régler le temps de sommeil, mais cela n'aide pas. Aussi, j'ajoute du code à poster. –

4

C'est parce que vous vous joignez à votre thread de travail. Votre thread d'interface utilisateur démarre le thread d'arrière-plan, puis appelle Join. Cela empêche le thread d'interface utilisateur d'effectuer d'autres actions.

Pendant ce temps, le thread d'arrière-plan effectue son travail et appelle Invoke, qui attend le thread d'interface utilisateur pour répondre. Étant donné que le thread d'interface utilisateur attend une jointure, il ne traitera jamais la demande à invoquer. D'où, l'impasse.

Ce que vous devez faire, c'est d'éliminer le Join et le MessageBox. Mettez le MessageBox dans sa propre fonction.

void NotifyDone() { 
    if(InvokeRequired) BeginInvoke((MethodInvoker) NotifyDone); 
    else { 
     // Perform any post-processing work 
     MessageBox.Show("Done", "Info"); 
    } 
} 

Lorsque le fil de fond est fait, il suffit d'appeler cette méthode (et éliminer l'électricité statique de ThreadProc).

private void ThreadProc(object obj) 
    { 
     var grid = (DataGridView)obj; 
     foreach (DataGridViewRow row in grid.Rows) 
     { 
      if (Parser.GetPreparationByClientNameForSynonims(row.Cells["Prep"].Value.ToString()) != null) 
       UpdateGridSafe(grid,row.Index,1); 
      Thread.Sleep(10); 
     } 
     NotifyDone(); 
    } 

Et comme tout le monde a déjà dit, l'utilisation du sommeil, surtout à un faible intervalle est soit dangereux, trompeur ou sans valeur. Je suis dans le compte ce camp sans valeur.

1

Vous pouvez également rencontrez des problèmes à accéder au réseau en même temps de fils différents. DataTables ne sont pas thread-safe, donc je suppose que DataGridView n'est pas non plus. Voici un exemple de code de this article on DataRow and Concurrency où vous utiliseriez Monitor.Enter et Montori.Exit pour obtenir une certaine concurrence en place.

public void DoWorkUpdatingRow(object state) 
    { 
     List<DataRow> rowsToWorkOn = (List<DataRow>)state; 
     foreach (DataRow dr in rowsToWorkOn) 
     { 
      Monitor.Enter(this); 
      try 
      { 
       dr["value"] = dr["id"] + " new value"; 
      } 
      finally 
      { 
       Monitor.Exit(this); 
      } 
     } 
    } 
Questions connexes