Quelle est la façon la plus simple de déplacer un peu de travail à la discussion de fond?

StackOverflow https://stackoverflow.com/questions/8808283

Question

Supposons que j'ai ce morceau de code.

foreach(string value in myList)
  {
      string result = TimeConsumingTask(value);
      //update UI
      listBox.Add(result);
  }

Je veux passer string result = TimeConsumingTask(value); à un fil d'arrière-plan. Quelle est la façon la plus simple mais efficace de déplacer ce fil à fond si

- Order does matter
- Order doesn't matter
Était-ce utile?

La solution

Une façon simple à l'ordre de garantie est de faire tous les TimeConsumingTasks séquentiellement dans le même fil d'arrière-plan.

Cela peut prendre un peu plus que d'exécuter plusieurs TimeConsumingTasks sur plusieurs threads, mais vous permet d'économiser beaucoup de maux de tête en essayant de synchroniser les résultats et produire des données dans l'ordre requis.

Si votre objectif principal est de libérer l'interface utilisateur afin que l'utilisateur peut continuer à travailler sur quelque chose d'autre, la différence de temps d'achèvement entre l'utilisation d'un fil et plusieurs threads est probablement négligeable, alors allez la route la plus simple - utiliser un seul thread d'arrière-plan pour tous les TimeConsumingTasks.

Si votre objectif principal est de faire le calcul des TimeConsumingTasks complets plus rapidement, puis exécuter plusieurs threads est probablement la meilleure approche, car il est plus susceptible de tirer profit de plusieurs noyaux et exécuter une partie des travaux en parallèle.

Pour préserver l'ordre dans les multiples threads cas, vous devrez commander les résultats vous. Vous pouvez maintenir une liste de température de l'accumulateur et insérer chaque résultat dans à l'emplacement approprié de résultat dans la liste (si l'emplacement / index est facilement calculé ou connu), ou que vous ajoutez une étape de traitement supplémentaire après tous les fils ont terminé à fusionner les multiples résultats dans le produit final dans l'ordre souhaité. En parallèle et distribué cercles de calcul, le procédé de fractionnement d'un problème en plusieurs morceaux à compléter en parallèle, puis en combinant les résultats dans un ordre cohérent est appelée « réduction de la carte ».

Autres conseils

Dans une application graphique telle que WPF, WinForms, Silverlight, ... vous pouvez utiliser un BackgroundWorker car il prend soin de marshaling les différentes callbacks qui se produisent sur le thread principal qui est important lors de la mise à jour de l'interface utilisateur. Dans ASP.NET, vous pouvez jeter un oeil à pages asynchrones .

Prenons un exemple, si l'ordre ne soit un travailleur de fond:

var worker = new BackgroundWorker();
worker.DoWork += (obj, args) =>
{
    args.Result = myList.Select(TimeConsumingTask).ToList();
};
worker.RunWorkerCompleted += (obj, args) =>
{
    var result = (IList<string>)args.Result;
    foreach(string value in result)
    {
        listBox.Add(result);
    }
}; 
worker.RunWorkerAsync();

Il y a beaucoup de façons de la peau ce chat. Ces dernières années, j'ai utilisé beaucoup, BackgroundWorker étant le plus commun et simple. Cependant à partir de maintenant, vous devez penser à la Async CTP est la manière aller. Au moins revoir introduction avant de décider. En termes de simplicité et de code propre, concis, il est une partie du code le plus chaud jamais à écrire. :)

J'utiliser une tâche. De cette façon, l'opération tourne en arrière-plan sans bloquer l'interface utilisateur. Assurez-vous, cependant, de mettre à jour la liste sur le thread principal. Je ne sais pas comment faire dans WinForms, mais dans WPF vous accomplissez à l'aide Dispatcher.Invoke() ou Dispatcher.BeginInvoke() comme ci-dessous.

Si les questions de commande:

Task.Factory.StartNew(
    () =>
        {
            foreach (string value in myList)
            {
                string result = TimeConsumingTask(value);
                //update UI
                Application.Current.Dispatcher.BeginInvoke(
                    (Action)(() => listBox.Add(result)));
            }            
        });

Si la commande n'a pas d'importance, vous pouvez également utiliser Parallel.ForEach().

Task.Factory.StartNew(
    () =>
        {
            Parallel.ForEach(myList, 
                value =>
                {
                    string result = TimeConsumingTask(value);
                    //update UI
                    Application.Current.Dispatcher.BeginInvoke(
                       (Action)(() => listBox.Add(result)));
                });            
        });
    }

Vous devez attendre le résultat avant de pouvoir mettre à jour l'interface utilisateur, donc il n'y a pas de sens dans le mouvement que la TimeConsumingTask () au fil d'arrière-plan. Vous aurez besoin de mettre à jour l'interface utilisateur du fil d'arrière-plan (mais l'appel à marshalling le thread d'interface utilisateur).

Cela dépend de votre but ultime, mais une solution simple est juste pour lancer une tâche. Cela fonctionne si l'ordre est important.

Task.Factory.StartNew(
    () => 
    {
        foreach(string value in myList)   
        {       
            string result = TimeConsumingTask(value);       
            listBox.Invoke(new Action(() => listBox.Add(result)));   
        } 
    });

L'utilisation BackgroundWorker est assez facile, mais pas aussi "simple", car il n'y a plus de code:

  • créer un BackgroundWorker
  • affecter un gestionnaire DoWork
  • affecter un gestionnaire d'avancement
  • set WorkerReportsProgress
  • appel RunWorkerAsync ()

Si l'ordre n'a pas d'importance, vous pouvez utiliser la boucle de Parallel.ForEach. ( Edité selon dthorpe et les commentaires de ttymatty ).

using System.Threading.Tasks;

var results = new List<string>();

Parallel.ForEach(myList, value =>
{
    string result = TimeConsumingTask(value);
    results.Add(result);
});

//update UI
listBox.AddRange(results);

Si vous voulez callback l'interface utilisateur avec des informations de processus, je vous recommande d'utiliser un BackgroundWorker comme Darin Dimitrov recommande dans sa réponse.

Il y a une grande différence entre le multithreading et le traitement de fond, mais vous pouvez les utiliser en même temps.

traitement de fond - en utilisant BackgroundWorker comme Darin suggère - vous pouvez permettre à l'utilisateur de continuer à travailler dans l'interface utilisateur sans attendre le processus d'arrière-plan complet. Si l'utilisateur doit attendre le traitement à la fin, avant de pouvoir, alors il n'y a pas de point en cours d'exécution en arrière-plan.

multithreading - en utilisant Library extensions parallèles , peut-être - vous pouvez utiliser plusieurs threads pour exécuter la boucle répétitive en parallèle de sorte qu'il se termine plus vite. Cela serait bénéfique si l'utilisateur attend la fin du processus avant de pouvoir continuer.

Enfin, si vous utilisez quelque chose en arrière-plan, vous pouvez l'obtenir pour terminer plus rapidement en utilisant le multithreading.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top