2010-02-08 4 views
0

J'ai un fil qui rassemble une liste d'URL à partir d'un site Web et met à jour l'interface utilisateur comme il le fait. Cela fonctionne bien. Mais, j'ai besoin du fil principal pour attendre que les liens aient été rassemblés. J'ai essayé de faire une jointure, mais cela bloque l'interface utilisateur. Voici mon exemple. Comme vous pouvez le voir, la boucle foreach est appelée en même temps que le thread. Je veux que le foreach fonctionne après que le thread ait fonctionné.Sujets et attente

Des idées?

/** This thread will add links to list<string> linkList **/ 
Thread linkListThread = new Thread(new ThreadStart(getLinkList)); 
linkListThread.Start(); 
foreach (String link in linkList) 
{ 
    txtOutput.Text += link; 
} 
+0

Une pointe de côté, vous pouvez faire quelque chose comme 'txtOutput.Text = string.join ("", linkList.ToArray()) ', beaucoup plus rapide et mieux sur la mémoire. –

+0

Si votre thread actuel engendre juste un autre thread pour faire le travail, et doit ensuite attendre que ce thread se termine, pourquoi faites-vous cela avec deux threads? Pourquoi ne pas simplement appeler le code qui fait le travail directement? –

Répondre

1

Déplacer le code qui doit être exécuté après que le fil a terminé dans un gestionnaire d'événements pour BackgroundWorker.RunWorkerCompleted

Mise à jour: Le gestionnaire est appelé à droite (appelant) fil - afin que vous puissiez en toute sécurité mettre à jour l'interface utilisateur.

Voir l'extrait de code sur la page msdn ci-dessus.

+1

Non, le code RunWorkerCompleted est appelé automatiquement dans le contexte de thread du formulaire par le worker d'arrière-plan. –

+0

La méthode que j'appelle ... getLinkList(), cela devrait-il être changé en private void getLinkList (Object expéditeur, args DoWorkEventArgs)? Je peux donc appeler bw.DoWork + = getLinkList; ? –

+0

Oui. Cependant, vous pouvez également double-cliquer sur l'événement DoWork dans le concepteur et copier le code de getLinkList() dans le gestionnaire d'événements nouvellement créé. N'oubliez pas: Ne gérez jamais les exceptions dans DoWork! Ils seront transmis à l'événement RunWorkerCompleted dans e.Error. –

6

Vous pouvez utiliser un arrière-plan. Ou demandez à la méthode thread d'appeler une méthode dans le contexte principal lorsqu'elle est terminée, en passant la liste des éléments que vous avez collectés.

EDIT
Je pensais que je devrais élaborer ma deuxième approche un peu.

Vous pouvez préparer une instance de liste avant de créer le fil:

List<string> links = new List<string>(); 

Ensuite, vous passez cette liste au fil, qui remplit:

Thread t = new Thread(new ParameterizedThreadStart(FillList)); 
t.Start(links); 

La méthode de fil prend la liste, remplissages et appelle une méthode qui montre les détails dans l'interface utilisateur:

private static void FillList(object state) 
{ 
    List<string> links = (List<string>)state; 

    // Fill data 

    this.Invoke((MethodInvoker)delegate() { HandleNewLinks(links); })); 
} 

le HandleNewLinks méthode fonctionne comme on peut s'y attendre:

private void HandleNewLinks(List<string> links) 
{ 
    foreach (string link in links) 
     // Do something... 
} 
+0

+1 - c'est ce que j'essayais de dire, donc j'ai supprimé ma réponse. N'oubliez pas que vous devrez sécuriser le thread du gestionnaire. – ChrisF

+0

Bien sûr, mais je suppose dans cet exemple que le thread n'est créé qu'une seule fois pour découpler le remplissage de la liste et montrer le résultat. Ma solution préférée serait un travailleur de fond de toute façon ... –

0

On ne sait pas ce que vous voulez: soit l'application attend (et ne répond pas), ou l'application n'attend pas et continue de répondre. Dans ce dernier cas, vous pouvez désactiver certains contrôles/actions possibles jusqu'à ce que la liste ait fini de charger.

Une solution de contournement sale est de faire une sorte de rotation en attente (Join avec un délai d'attente de quelques ms, renvoie le résultat si c'est fait ou non) avec certains Application.DoEvents() appels.

+0

Je veux que l'application reste sensible oui. Aurais-je besoin de créer 2 threads et de les rejoindre? C'est à dire. le fil pour rassembler les liens, et un fil pour sortir? Va-t-il attendre que le thread soit complètement terminé? –

+0

Le thread UI fera la sortie (vous ne devriez pas accéder aux contrôles WinForms à partir d'un autre thread), et l'autre thread fait le travail (obtenir les liens). Ainsi, le thread UI a 'while (! WorkerThread.Join (20)) {Application.DoEvents(); } 'qui attendra jusqu'à ce que l'autre thread soit terminé tout en gardant le message pump (et donc l'interface utilisateur) en cours d'exécution. – Lucero

0

Quelque chose de simple serait d'avoir vos threads de travail appellent à l'application principale à la fin, alors vous garder un nombre de threads remplis et dans votre interface principale faire quelque chose comme:

while(runningThreads != 0) 
{ 
    Application.DoEvents(); 
} 

et ont les fils appelez:

void OnThreadCompleted() 
{ 
    runningThreads--; 
} 

Mieux vaut utiliser BackgroundWorker pour ce lieu de créer vos propres fils comme cela a tous les mécanismes de rappel prêts à aller.

0

Nous avons utilisé le travailleur de fond pour quelque chose de semblable et il a bien fonctionné, avec deux observations:

  1. Ne pas ajouter du texte à une zone de texte avec + = car il vous ralentir considérablement après quelques cent lignes. Utilisez AppendText à la place.

  2. Si vous ajoutez beaucoup d'informations à l'interface et que vous avez des temps de sommeil (pendant le traitement), le thread risque de s'endormir. Nous l'avons corrigé en supprimant le texte dans la zone de texte toutes les 200 lignes (les résultats ont été écrits dans un fichier, donc nous n'avons rien perdu).

0

Une alternative est d'utiliser simplement Invoke sur le thread principal:

void YourMethod() 
{ 
    Thread linkListThread = new Thread(new ThreadStart(getLinkList)); 
    linkListThread.Start(); 
} 

void getLinkList() 
{ 
    List<string> linkList = new List<string>(); 
    // Your tasks 

    // Done 
    LinkListComplete(linkList); 
} 

void LinkListComplete(List<string> linkList) 
{ 
    if (InvokeRequired) 
    { 
     Invoke(new Action<List<string>>(LinkListComplete),linkList); 
     return; 
    } 

    foreach (String link in linkList) 
    { 
     txtOutput.Text += link; 
    } 
}