Comment exécuter un simple bout de code dans un nouveau thread?


340

J'ai un peu de code que je dois exécuter dans un thread différent de l'interface graphique car il provoque actuellement le gel du formulaire pendant l'exécution du code (environ 10 secondes).

Supposons que je n'ai jamais créé de nouveau thread auparavant; quel est un exemple simple / basique de la façon de procéder en C # et en utilisant .NET Framework 2.0 ou version ultérieure?


2
La plupart des réponses ici étaient bonnes à l'époque, mais les améliorations dans .NET Framework 4.0 simplifient les choses. Vous pouvez utiliser la méthode Task.Run (), comme décrit dans cette réponse: stackoverflow.com/a/31778592/1633949
Richard II

Réponses:


336

Un bon endroit pour commencer à lire est Joe Albahari .

Si vous souhaitez créer votre propre thread, c'est aussi simple que cela:

using System.Threading;
new Thread(() => 
{
    Thread.CurrentThread.IsBackground = true; 
    /* run your code here */ 
    Console.WriteLine("Hello, world"); 
}).Start();

@EdPower, cela s'applique-t-il uniquement à Winforms .. ou cela fonctionnera-t-il dans Web Forms ..?
MethodMan

@MethodMan - Oui, cela fonctionnera dans les formulaires Web. Commencez ici:
Ed Power

9
Soyez prudent lorsque vous définissez la valeur IsBackgroundtrue. Il ne fait probablement pas ce que vous pensez qu'il fait. Ce qu'il fait, c'est de configurer si le thread sera tué lorsque tous les threads de premier plan seront morts ou s'il maintiendra l'application en vie. Si vous ne voulez pas que votre thread se termine à mi-exécution, ne définissez pas IsBackgroundsur true.
Zero3

10
@EdPower Je pense que vous devez faire attention de toute façon! Un exemple de tâche que vous ne voulez probablement pas terminer en cours d'exécution est celle qui enregistre les données sur le disque. Mais bien sûr, si votre tâche peut être interrompue à tout moment, le drapeau est correct. Mon point était juste qu'il faut être prudent lors de l'utilisation du drapeau , car vous n'avez pas décrit son objectif, et sa dénomination pourrait facilement faire croire qu'il fait autre chose que ce qu'il fait réellement.
Zero3

3
avec .NET Framework 4.0+, utilisez simplement Task.Run (), comme décrit dans cette réponse: stackoverflow.com/a/31778592/1633949
Richard II

193

BackgroundWorker semble être le meilleur choix pour vous.

Voici mon exemple minimal. Après avoir cliqué sur le bouton, le travailleur d'arrière-plan commencera à travailler dans le fil d'arrière-plan et signalera également sa progression simultanément. Il fera également rapport à la fin des travaux.

using System.ComponentModel;
...
    private void button1_Click(object sender, EventArgs e)
    {
        BackgroundWorker bw = new BackgroundWorker();

        // this allows our worker to report progress during work
        bw.WorkerReportsProgress = true;

        // what to do in the background thread
        bw.DoWork += new DoWorkEventHandler(
        delegate(object o, DoWorkEventArgs args)
        {
            BackgroundWorker b = o as BackgroundWorker;

            // do some simple processing for 10 seconds
            for (int i = 1; i <= 10; i++)
            {
                // report the progress in percent
                b.ReportProgress(i * 10);
                Thread.Sleep(1000);
            }

        });

        // what to do when progress changed (update the progress bar for example)
        bw.ProgressChanged += new ProgressChangedEventHandler(
        delegate(object o, ProgressChangedEventArgs args)
        {
            label1.Text = string.Format("{0}% Completed", args.ProgressPercentage);
        });

        // what to do when worker completes its task (notify the user)
        bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(
        delegate(object o, RunWorkerCompletedEventArgs args)
        {
            label1.Text = "Finished!";
        });

        bw.RunWorkerAsync();
    }

Remarque:

  • Je mets tout en méthode unique en utilisant la méthode anonyme de C # pour plus de simplicité, mais vous pouvez toujours les extraire vers différentes méthodes.
  • Il est sûr de mettre à jour l'interface graphique dans ProgressChangedou RunWorkerCompleted gestionnaires. Cependant, la mise à jour de l'interface graphique à partir de DoWork entraînera InvalidOperationException.

27
Using System.ComponentModel; (pourrait sauver les gens de faire une recherche google, merci pour cet exemple de code utile) +1
sooprise

@Gant Merci pour l'exemple de code. Lorsque j'ai essayé votre code, l'événement ProgressChanged ne s'est pas déclenché. Cependant, quand j'ai essayé la réponse acceptée similaire ici [ stackoverflow.com/questions/23112676/… cela a fonctionné.
Martin

1
@sooprise Ctrl +. vous aide dans ces situations! (En supposant que vous utilisez Visual Studio)
SepehrM

100

Le ThreadPool.QueueUserWorkItem est assez idéal pour quelque chose de simple. La seule mise en garde est d'accéder à un contrôle à partir de l'autre thread.

System.Threading.ThreadPool.QueueUserWorkItem(delegate {
    DoSomethingThatDoesntInvolveAControl();
}, null);

Aussi mon préféré. Une ligne rapide pour la jonction . Je l'ai en numérotation abrégée (extrait). Fonctionne très bien avec les contrôles. Vous avez juste besoin de savoir comment utiliser Invoke et InvokeRequired .
Bitterblue

1
+1 Notez que le délégué vous permet également de bien emballer les paramètres.
Will Bickford

comment utiliser ThreadPool.QueueUserWorkItem avec la fonction de rappel signifie que lorsque le travail est terminé, le rappel nous en informera.
Mou

76

Rapide et sale, mais cela fonctionnera:

Utilisation en haut:

using System.Threading;

code simple:

static void Main( string[] args )
{
    Thread t = new Thread( NewThread );
    t.Start();
}

static void NewThread()
{
    //code goes here
}

Je viens de le jeter dans une nouvelle application console pour un exmaple


8
N'oubliez pas que les threads marqués IsBackground ne sont pas automatiquement arrêtés par le Runtime. Cela nécessite la gestion des threads par l'application. Si vous laissez le thread marqué comme n'étant pas d'arrière-plan, après l'exécution des threads, le thread est terminé pour vous.
CmdrTallen

14
@CmdrTallen: Ce n'est pas tout à fait vrai. Un thread marqué IsBackground = true signifie que le thread n'empêchera pas le processus de se terminer, c'est-à-dire qu'un processus se terminera lorsque tous les threads avec IsBackground = false seront terminés
Phil Devaney

66

Voici une autre option:

Task.Run(()=>{
//Here is a new thread
});

1
si vous créez un thread de cette façon, comment arrêteriez-vous ce thread à partir du thread d'interface utilisateur?
slayernoah

1
@slayernoah, vous pouvez lui passer un CancellationTokenSource en tant que paramètre et définir le jeton d'annulation en dehors du thread: msdn.microsoft.com/en-us/library/dd997364(v=vs.110).aspx
Spongebob Comrade

7
Ce code ne vous garantit pas d'avoir un nouveau thread. La décision dépend de l'implémentation actuelle du ThreadPool que vous utilisez. Voir comme exemple stackoverflow.com/questions/13570579/…
Giulio Caccin

3
@GiulioCaccin Il est possible pour ThreadPool de choisir un thread existant dans son pool mais c'est certainement un thread différent.
Spongebob Comrade

40

Essayez d'utiliser la classe BackgroundWorker . Vous lui donnez des délégués pour ce qu'il faut exécuter et pour être averti lorsque le travail est terminé. Il y a un exemple sur la page MSDN auquel j'ai lié.


6
Vous pouvez certainement le faire avec la classe Thread, mais BackgroundWorker vous donne des méthodes pour l'achèvement des threads et des rapports de progression que vous auriez autrement à trouver comment vous utiliser. N'oubliez pas que vous devez utiliser Invoke pour parler à l'interface utilisateur!
Robert Rossney

1
Soyez averti: BackgroundWorker a quelques limites subtiles, mais pour le commun "je veux partir et faire quelque chose et que mon formulaire reste réactif" c'est fantastique.
Merus

10
@Merus, pourriez-vous nous expliquer quelles sont ces limitations subtiles?
Christian Hudon

14

Si vous souhaitez obtenir une valeur:

var someValue;

Thread thread = new Thread(delegate()
            {                 
                //Do somthing and set your value
                someValue = "Hello World";
            });

thread.Start();

while (thread.IsAlive)
  Application.DoEvents();

6

Mettez ce code dans une fonction (le code qui ne peut pas être exécuté sur le même thread que l'interface graphique), et pour déclencher l'exécution de ce code, mettez ce qui suit.

Thread myThread= new Thread(nameOfFunction);

workerThread.Start();

L'appel de la fonction start sur l'objet thread entraînera l'exécution de votre appel de fonction dans un nouveau thread.


3
@ user50612 De quoi parlez-vous? Le nouveau thread s'exécutera nameOfFunctionen arrière-plan et non sur le thread GUI actuel. La IsBackgroundpropriété détermine si le thread maintiendra l'application en vie ou non: msdn.microsoft.com/en-us/library/…
Zero3

5

Voici comment utiliser des threads avec une barre de progression, c'est juste pour comprendre comment les threads fonctionnent, sous la forme il y a trois barres de progression et 4 boutons:

 public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }
    Thread t, t2, t3;
    private void Form1_Load(object sender, EventArgs e)
    {

        CheckForIllegalCrossThreadCalls = false;

         t = new Thread(birinicBar); //evry thread workes with a new progressBar


         t2 = new Thread(ikinciBar);


         t3 = new Thread(ucuncuBar);

    }

    public void birinicBar() //to make progressBar work
    {
        for (int i = 0; i < 100; i++) {
            progressBar1.Value++;
            Thread.Sleep(100); // this progressBar gonna work faster
        }
    }

    public void ikinciBar()
    {
        for (int i = 0; i < 100; i++)
        {
            progressBar2.Value++;
            Thread.Sleep(200);
        }


    }

    public void ucuncuBar()
    {
        for (int i = 0; i < 100; i++)
        {
            progressBar3.Value++;
            Thread.Sleep(300);
        }
    }

    private void button1_Click(object sender, EventArgs e) //that button to start the threads
    {
        t.Start();
        t2.Start(); t3.Start();

    }

    private void button4_Click(object sender, EventArgs e)//that button to stup the threads with the progressBar
    {
        t.Suspend();
        t2.Suspend();
        t3.Suspend();
    }

    private void button2_Click(object sender, EventArgs e)// that is for contuniue after stuping
    {
        t.Resume();
        t2.Resume();
        t3.Resume();
    }

    private void button3_Click(object sender, EventArgs e) // finally with that button you can remove all of the threads
    {
        t.Abort();
        t2.Abort();
        t3.Abort();
    }
}

3
// following declaration of delegate ,,,
public delegate long GetEnergyUsageDelegate(DateTime lastRunTime, 
                                            DateTime procDateTime);

// following inside of some client method
GetEnergyUsageDelegate nrgDel = GetEnergyUsage;
IAsyncResult aR = nrgDel.BeginInvoke(lastRunTime, procDT, null, null);
while (!aR.IsCompleted) Thread.Sleep(500);
int usageCnt = nrgDel.EndInvoke(aR);

Charles votre code (ci-dessus) n'est pas correct. Vous n'avez pas besoin de tourner pour attendre la fin. EndInvoke se bloquera jusqu'à ce que le WaitHandle soit signalé.

Si vous souhaitez bloquer jusqu'à la fin, vous devez simplement

nrgDel.EndInvoke(nrgDel.BeginInvoke(lastRuntime,procDT,null,null));

Ou bien

ar.AsyncWaitHandle.WaitOne();

Mais quel est l'intérêt d'émettre des appels anyc si vous bloquez? Vous pourriez tout aussi bien utiliser un appel synchrone. Un meilleur pari serait de ne pas bloquer et passer une lambda pour le nettoyage:

nrgDel.BeginInvoke(lastRuntime,procDT,(ar)=> {ar.EndInvoke(ar);},null);

Une chose à garder à l'esprit est que vous devez appeler EndInvoke. Beaucoup de gens oublient cela et finissent par perdre le WaitHandle car la plupart des implémentations asynchrones libèrent le waithandle dans EndInvoke.


2

Si vous allez utiliser l'objet Thread brut, vous devez définir IsBackground sur true au minimum et vous devez également définir le modèle Threading Apartment (probablement STA).

public static void DoWork()
{
    // do some work
}

public static void StartWorker()
{
    Thread worker = new Thread(DoWork);
    worker.IsBackground = true;
    worker.SetApartmentState(System.Threading.ApartmentState.STA);
    worker.Start()
}

Je recommanderais la classe BackgroundWorker si vous avez besoin d'une interaction avec l'interface utilisateur.


Soyez prudent lorsque vous définissez IsBackground sur true. Il ne fait probablement pas ce que vous pensez qu'il fait. Ce qu'il fait, c'est de configurer si le thread sera tué lorsque tous les threads de premier plan seront morts ou s'il maintiendra l'application en vie. Si vous ne voulez pas que votre thread se termine à mi-exécution, ne définissez pas IsBackground sur true.
Zero3


1

une autre option, qui utilise des délégués et le Thread Pool ...

en supposant que «GetEnergyUsage» est une méthode qui prend un DateTime et un autre DateTime comme arguments d'entrée, et retourne un Int ...

// following declaration of delegate ,,,
public delegate long GetEnergyUsageDelegate(DateTime lastRunTime, 
                                            DateTime procDateTime);

// following inside of some client method 
GetEnergyUsageDelegate nrgDel = GetEnergyUsage;                     
IAsyncResult aR = nrgDel.BeginInvoke(lastRunTime, procDT, null, null);
while (!aR.IsCompleted) Thread.Sleep(500);
int usageCnt = nrgDel.EndInvoke(aR);

1
Charles, votre code est-il fonctionnellement différent de int usageCnt = nrgDel.Invoke(lastRunTime, procDT, null, null);? On dirait qu'il suspend de toute façon le thread actuel avec le sommeil ... Je pensais que cela ne
servirait à

Oui, BeginInvoke appelle le délégué sur un autre thread du pool de threads ... Si vous utilisez Invoke, le délégué est appelé de manière synchrone sur le thread actuel ... Mais vous avez raison, le sommeil est incorrect ... vous devez éliminer cela et recueillir les résultats à l'aide d'une fonction de rappel. Je vais modifier pour montrer que
Charles Bretana

1

Il existe de nombreuses façons d'exécuter des threads distincts dans .Net, chacun a des comportements différents. Avez-vous besoin de continuer à exécuter le thread après la fermeture de l'interface graphique? Avez-vous besoin de transmettre des informations entre le thread et l'interface graphique? Le thread doit-il mettre à jour l'interface graphique? Le thread doit-il effectuer une tâche puis se fermer ou doit-il continuer à s'exécuter? Les réponses à ces questions vous diront quelle méthode utiliser.

Il existe un bon article sur la méthode asynchrone sur la sur le site Web Code Project qui décrit les différentes méthodes et fournit un exemple de code.

Notez que cet article a été écrit avant l' introduction du modèle async / attente et de la bibliothèque parallèle de tâches dans .NET.


C'est le type d'informations que je recherchais, mais votre réponse est victime de pourriture de lien.
Chris

Merci de m'avoir fait savoir, @Chris; lien fixe.
Dour High Arch

0

Comment: utiliser un fil d'arrière-plan pour rechercher des fichiers

Vous devez être très prudent avec l'accès à partir d'autres threads à des choses spécifiques à l'interface graphique (cela est courant pour de nombreuses boîtes à outils GUI). Si vous souhaitez mettre à jour quelque chose dans l'interface graphique à partir du thread de traitement, vérifiez cette réponse qui, je pense, est utile pour WinForms. Pour WPF, voyez ceci (il montre comment toucher le composant dans la méthode UpdateProgress () pour qu'il fonctionne à partir d'autres threads, mais en fait je n'aime pas qu'il ne le fasse pas CheckAccess()avant de le faire BeginInvokevia Dispathcer, voir et rechercher CheckAccess dedans)

Je cherchais un livre spécifique à .NET sur le filetage et j'ai trouvé celui-ci (téléchargeable gratuitement). Voir http://www.albahari.com/threading/ pour plus de détails à ce sujet.

Je crois que vous trouverez ce dont vous avez besoin pour lancer l'exécution en tant que nouveau fil dans les 20 premières pages et il en a beaucoup plus (je ne suis pas sûr des extraits spécifiques à l'interface graphique, je veux dire strictement spécifiques au filetage). Serait heureux d'entendre ce que la communauté pense de ce travail parce que je lis celui-ci. Pour l'instant, cela me semblait plutôt bien (pour montrer les méthodes et les types spécifiques à .NET pour le filetage). Il couvre également .NET 2.0 (et non l'ancienne 1.1) ce que j'apprécie vraiment.


0

Je recommande de consulter la bibliothèque Power Threading de Jeff Richter et en particulier l'IAsyncEnumerator. Jetez un œil à la vidéo sur Charlie Calvert's blog de où Richter passe en revue pour un bon aperçu.

Ne vous laissez pas rebuter par le nom car cela rend les tâches de programmation asynchrones plus faciles à coder.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.