Come posso eseguire un semplice bit di codice in un nuovo thread?


340

Ho un po 'di codice che devo eseguire in un thread diverso rispetto alla GUI in quanto attualmente causa il blocco del modulo mentre il codice viene eseguito (circa 10 secondi).

Supponiamo che non abbia mai creato un nuovo thread prima; qual è un esempio semplice / di base di come eseguire questa operazione in C # e utilizzare .NET Framework 2.0 o versioni successive?


2
La maggior parte delle risposte qui erano buone al momento, ma i miglioramenti in .NET Framework 4.0 semplificano le cose. È possibile utilizzare il metodo Task.Run (), come descritto in questa risposta: stackoverflow.com/a/31778592/1633949
Richard II,

Risposte:


336

Un buon posto per iniziare a leggere è Joe Albahari .

Se vuoi creare il tuo thread, questo è semplice come si ottiene:

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

@EdPower, questo vale solo per Winforms .. o funzionerà in Web Forms??
MethodMan,

@MethodMan - Sì, funzionerà in Web Forms. Inizia qui:
Ed Power

9
Fai attenzione a impostare IsBackgroundsu true. Probabilmente non fa quello che pensi che faccia. Ciò che fa è configurare se il thread verrà ucciso quando tutti i thread in primo piano sono morti o se il thread manterrà l'applicazione in vita. Se non vuoi che il tuo thread sia terminato a metà dell'esecuzione, non impostare IsBackgroundsu true.
Zero3,

10
@EdPower Penso che dovresti stare attento in entrambi i modi! Un esempio di un'attività che probabilmente non si desidera interrompere a metà dell'esecuzione è quella che salva i dati sul disco. Ma certo, se il tuo compito è adatto per la risoluzione in qualsiasi momento, la bandiera va bene. Il mio punto era solo che bisogna stare attenti all'utilizzo della bandiera , dal momento che non è stato descritto il suo scopo, e la sua denominazione potrebbe facilmente portare a credere che fa qualcosa di diverso da quello che effettivamente fa.
Zero3,

3
con .NET Framework 4.0+ basta usare Task.Run (), come descritto in questa risposta: stackoverflow.com/a/31778592/1633949
Richard II,

193

BackgroundWorker sembra essere la scelta migliore per te.

Ecco il mio esempio minimo. Dopo aver fatto clic sul pulsante, il lavoratore in background inizierà a lavorare nel thread in background e riporterà contemporaneamente i suoi progressi. Riferirà anche dopo il completamento del lavoro.

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();
    }

Nota:

  • Ho messo tutto in un unico metodo usando il metodo anonimo di C # per semplicità, ma puoi sempre estrarlo in diversi metodi.
  • È sicuro aggiornare la GUI all'interno ProgressChangedo i RunWorkerCompletedgestori. Tuttavia, l'aggiornamento della GUI da DoWork causa InvalidOperationException.

27
Utilizzando System.ComponentModel; (potrebbe salvare le persone dal fare una ricerca su google, grazie per questo utile esempio di codice) +1
sooprise

@Gant Grazie per il codice di esempio. Quando ho provato il tuo codice, l'evento ProgressChanged non veniva generato. Tuttavia, quando ho provato la risposta accettata simile qui [ stackoverflow.com/questions/23112676/… ha funzionato.
Martin,

1
@sooprise Ctrl +. ti aiuta in queste situazioni! (Supponendo che tu stia utilizzando Visual Studio)
SepehrM

100

Il ThreadPool.QueueUserWorkItem è abbastanza ideale per qualcosa di semplice. L'unica avvertenza è accedere a un controllo dall'altro thread.

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

Anche il mio preferito. Una linea veloce per l' incrocio . Ce l'ho su composizione rapida (snippet). Funziona molto bene con i controlli. Devi solo sapere come usare Invoke e InvokeRequired .
Bitterblue,

1
+1 Si noti che il delegato consente anche di avvolgere i parametri in modo ordinato.
Will Bickford,

come utilizzare ThreadPool.QueueUserWorkItem con la funzione di richiamata significa che quando il lavoro fatto, quindi la richiamata ci avviseranno.
Mou,

76

Veloce e sporco, ma funzionerà:

Utilizzando in alto:

using System.Threading;

codice semplice:

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

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

L'ho appena lanciato in una nuova applicazione console per un esempio


8
Ricorda che i thread contrassegnati IsBackground non vengono automaticamente chiusi dal runtime. Ciò richiede la gestione dei thread da parte dell'app. Se lasci il thread contrassegnato come non di sfondo, dopo l'esecuzione dei thread il thread viene terminato per te.
CmdrTallen,

14
@CmdrTallen: Non è del tutto giusto. Un thread contrassegnato da IsBackground = true significa che il thread non impedirà la chiusura del processo, ovvero un processo verrà chiuso quando tutti i thread con IsBackground = false sono usciti
Phil Devaney,

66

Ecco un'altra opzione:

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

1
se crei un thread in questo modo, come interromperesti questo thread dal thread dell'interfaccia utente?
slayernoah,

1
@slayernoah è possibile passare una cancellazioneTokenSource come parametro e impostare il token di cancellazione all'esterno del thread: msdn.microsoft.com/en-us/library/dd997364(v=vs.110).aspx
Spongebob Comprade

7
Questo codice non ti garantirà di avere un nuovo thread. La decisione spetta all'attuale implementazione del ThreadPool che si sta utilizzando. Vedi come esempio stackoverflow.com/questions/13570579/…
Giulio Caccin

3
@GiulioCaccin È possibile che ThreadPool scelga un thread esistente dal suo pool ma è sicuramente un thread diverso.
Compagno di Spongebob,

40

Prova a utilizzare la classe BackgroundWorker . Gli dai dei delegati per cosa eseguire e ricevi una notifica al termine del lavoro. C'è un esempio sulla pagina MSDN a cui ho collegato.


6
Puoi certamente farlo con la classe Thread, ma BackgroundWorker ti offre metodi per il completamento del thread e i rapporti sui progressi che altrimenti dovresti capire come usare te stesso. Non dimenticare che devi usare Invoke per parlare con l'interfaccia utente!
Robert Rossney,

1
Attenzione: BackgroundWorker ha alcune sottili limitazioni, ma per il comune "Voglio andare via e fare qualcosa e mantenere la mia forma reattiva" è fantastico.
Merus,

10
@Merus, potresti approfondire quali sono questi sottili limiti?
Christian Hudon,

14

Se vuoi ottenere un valore:

var someValue;

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

thread.Start();

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

6

Inserisci quel codice in una funzione (il codice che non può essere eseguito sullo stesso thread della GUI) e per attivare l'esecuzione di quel codice inserisci quanto segue.

Thread myThread= new Thread(nameOfFunction);

workerThread.Start();

Chiamare la funzione di avvio sull'oggetto thread provoca l'esecuzione della chiamata di funzione in un nuovo thread.


3
@ user50612 Di cosa stai parlando? Il nuovo thread verrà eseguito nameOfFunctionin background e non sul thread della GUI corrente. La IsBackgroundproprietà determina se il filo non mancherà di tenere l'applicazione vivo o no: msdn.microsoft.com/en-us/library/...
Zero3

5

Ecco come utilizzare i thread con progressBar, è solo per capire come funzionano i thread, nella forma ci sono tre progressBar e il pulsante 4:

 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 il tuo codice (sopra) non è corretto. Non è necessario girare in attesa del completamento. EndInvoke si bloccherà fino alla segnalazione di WaitHandle.

Se vuoi bloccare fino al completamento devi semplicemente farlo

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

o in alternativa

ar.AsyncWaitHandle.WaitOne();

Ma qual è lo scopo di emettere eventuali chiamate in caso di blocco? Potresti anche usare una chiamata sincrona. Una scommessa migliore sarebbe quella di non bloccare e passare un lambda per la pulizia:

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

Una cosa da tenere a mente è che è necessario chiamare EndInvoke. Molte persone lo dimenticano e finiscono col perdere WaitHandle poiché la maggior parte delle implementazioni asincrone rilasciano il waithandle in EndInvoke.


2

Se stai per utilizzare l'oggetto Thread non elaborato, devi impostare IsBackground su true come minimo e devi anche impostare il modello Threading Apartment (probabilmente 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()
}

Consiglierei la classe BackgroundWorker se hai bisogno di interazione con l'interfaccia utente.


Fare attenzione a impostare IsBackground su true. Probabilmente non fa quello che pensi che faccia. Ciò che fa è configurare se il thread verrà ucciso quando tutti i thread in primo piano sono morti o se il thread manterrà l'applicazione in vita. Se non vuoi che il tuo thread sia terminato a metà dell'esecuzione, non impostare IsBackground su true.
Zero3,


1

un'altra opzione, che utilizza delegati e il pool di thread ...

supponendo che 'GetEnergyUsage' sia un metodo che accetta un DateTime e un altro DateTime come argomenti di input e restituisce 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, il tuo codice è funzionalmente diverso da int usageCnt = nrgDel.Invoke(lastRunTime, procDT, null, null);? Sembra che sospenda comunque il thread corrente con sleep ... Pensavo che non avrebbe aiutato nulla con il congelamento della GUI se lo
chiamavi

Sì, BeginInvoke chiama il delegato su un altro thread dal pool di thread ... Se usi Invoke, il delegato viene chiamato in modo sincrono sul thread corrente ... Ma hai ragione, il sonno è sbagliato ... dovresti eliminarlo e raccogliere i risultati utilizzando una funzione di richiamata.
Modificherò

1

Esistono molti modi per eseguire thread separati in .Net, ognuno con comportamenti diversi. Devi continuare a eseguire il thread dopo aver chiuso la GUI? Devi passare informazioni tra il thread e la GUI? Il thread deve aggiornare la GUI? Il thread dovrebbe chiudere un'attività o dovrebbe continuare a funzionare? Le risposte a queste domande ti diranno quale metodo utilizzare.

Esiste un buon articolo sul metodo asincrono nel sito Web Code Project che descrive i vari metodi e fornisce un codice di esempio.

Si noti che questo articolo è stato scritto prima che il modello asincrono / waitit e Task Parallel Library fossero introdotti in .NET.


Questo è il tipo di informazioni che stavo cercando, ma la tua risposta è una vittima del marciume dei link.
Chris,

Grazie per avermelo fatto notare, @Chris; collegamento fisso.
Dour High Arch,

0

Procedura: utilizzare un thread in background per cercare file

Devi stare molto attento con l'accesso da altri thread a elementi specifici della GUI (è comune per molti toolkit GUI). Se vuoi aggiornare qualcosa nella GUI dall'elaborazione del thread, controlla questa risposta che ritengo utile per WinForms. Per WPF vedere questo (mostra come toccare il componente nel metodo UpdateProgress () in modo che funzioni da altri thread, ma in realtà non mi piace che non lo faccia CheckAccess()prima di fare BeginInvoketramite Dispathcer, vedere e cercare CheckAccess in esso)

Stavo cercando un libro specifico su .NET per il threading e ho trovato questo (scaricabile gratuitamente). Vedi http://www.albahari.com/threading/ per maggiori dettagli al riguardo.

Credo che troverai ciò di cui hai bisogno per avviare l'esecuzione come nuovo thread nelle prime 20 pagine e ne ha molti di più (non sono sicuro dei frammenti specifici della GUI intendo strettamente specifici del threading). Sarei felice di sapere cosa pensa la comunità di questo lavoro perché sto leggendo questo. Per ora sembrava abbastanza pulito per me (per mostrare metodi e tipi specifici di .NET per il threading). Inoltre copre .NET 2.0 (e non antico 1.1) ciò che apprezzo molto.


0

Consiglio di dare un'occhiata alla libreria di thread di alimentazione di Jeff Richter e in particolare a IAsyncEnumerator. Dai un'occhiata al video sul blog di Charlie Calvert in cui Richter lo esamina per una buona panoramica.

Non lasciarti scoraggiare dal nome perché semplifica la codifica delle attività di programmazione asincrona.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.