Jak uruchomić prosty fragment kodu w nowym wątku?


340

Mam trochę kodu, który muszę uruchomić w innym wątku niż GUI, ponieważ obecnie powoduje zawieszanie się formularza podczas działania kodu (około 10 sekund).

Załóżmy, że nigdy wcześniej nie tworzyłem nowego wątku; jaki jest prosty / podstawowy przykład tego, jak to zrobić w języku C # i przy użyciu .NET Framework 2.0 lub nowszej?


2
Większość odpowiedzi tutaj była wtedy dobra, ale ulepszenia w .NET Framework 4.0 upraszczają sprawę. Możesz użyć metody Task.Run (), jak opisano w tej odpowiedzi: stackoverflow.com/a/31778592/1633949
Richard II

Odpowiedzi:


336

Dobrym miejscem do rozpoczęcia czytania jest Joe Albahari .

Jeśli chcesz utworzyć własny wątek, jest to tak proste, jak to możliwe:

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

@EdPower, czy dotyczy to tylko Winforms .. czy będzie działać w formularzach internetowych ..?
MethodMan

@MethodMan - Tak, będzie działać w formularzach internetowych. Zacznij tutaj:
Ed Power

9
Uważaj na ustawienie wartości IsBackgroundtrue. Prawdopodobnie nie działa tak, jak myślisz. To, co robi, to konfigurowanie, czy wątek zostanie zabity, gdy wszystkie wątki pierwszego planu zginą, czy też wątek utrzyma aplikację przy życiu. Jeśli nie chcesz, aby Twój wątek zakończył się w trakcie wykonywania, nie ustawiaj wartości IsBackgroundtrue.
Zero3

10
@EdPower Myślę, że i tak powinieneś być ostrożny! Jednym z przykładów zadania, którego prawdopodobnie nie chcesz zakończyć w trakcie wykonywania, jest zapisywanie danych na dysku. Ale oczywiście, jeśli twoje zadanie jest odpowiednie do zakończenia w dowolnym momencie, flaga jest w porządku. Chodziło mi po prostu o to, aby zachować ostrożność przy korzystaniu z flagi , ponieważ nie opisałeś jej celu, a jej nazwa może łatwo doprowadzić do przekonania, że ​​robi coś innego niż to, co faktycznie robi.
Zero3

3
z .NET Framework 4.0+ wystarczy użyć Task.Run (), zgodnie z opisem w odpowiedzi: stackoverflow.com/a/31778592/1633949
Richard II

193

BackgroundWorker wydaje się być najlepszym wyborem dla Ciebie.

Oto mój minimalny przykład. Po kliknięciu przycisku pracownik w tle rozpocznie pracę w wątku w tle i jednocześnie będzie raportować swoje postępy. Będzie także raportować po zakończeniu pracy.

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

Uwaga:

  • Dla uproszczenia umieściłem wszystko w jednej metodzie za pomocą anonimowej metody C #, ale zawsze możesz wyciągnąć je na różne metody.
  • Aktualizowanie GUI w programie obsługi ProgressChangedlub w programie RunWorkerCompletedobsługi jest bezpieczne . Jednak aktualizacja GUI DoWork spowoduje InvalidOperationException.

27
Korzystanie z System.ComponentModel; (może uratować ludzi przed wyszukiwaniem w Google, dziękuję za ten pomocny przykładowy kod) +1
sooprise

@Gant Dziękujemy za przykładowy kod. Kiedy próbowałem kodu, zdarzenie ProgressChanged nie było uruchamiane. Jednak gdy spróbowałem tutaj podobnej akceptowanej odpowiedzi [ stackoverflow.com/questions/23112676/... zadziałało.
Martin

1
@sooprise Ctrl +. pomaga w takich sytuacjach! (Zakładając, że używasz Visual Studio)
SepehrM

100

ThreadPool.QueueUserWorkItem jest całkiem idealne do czegoś prostego. Jedynym zastrzeżeniem jest dostęp do kontrolki z drugiego wątku.

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

Również mój ulubiony. Jedna szybka linia do skrzyżowania . Mam go na szybkiego wybierania (fragment). Działa bardzo dobrze z kontrolkami. Musisz tylko wiedzieć, jak korzystać z Invoke i InvokeRequired .
Bitterblue,

1
+1 Zauważ, że delegat pozwala również starannie zawinąć parametry.
Will Bickford,

jak korzystać z ThreadPool.QueueUserWorkItem z funkcją wywołania zwrotnego oznacza, że ​​po zakończeniu zadania oddzwonienie powiadomi nas.
Mou

76

Szybki i brudny, ale zadziała:

U góry:

using System.Threading;

prosty kod:

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

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

Właśnie wrzuciłem to do nowej aplikacji konsolowej na przykład


8
Pamiętaj, że wątki oznaczone IsBackground nie są automatycznie przerywane przez środowisko wykonawcze. Wymaga to zarządzania wątkami przez aplikację. Jeśli pozostawisz wątek oznaczony jako niebędący tłem, po wykonaniu wątku wątek zostanie dla Ciebie zakończony.
CmdrTallen,

14
@CmdrTallen: To nie do końca prawda. Wątek oznaczone IsBackground = true oznacza, że wątek nie będzie zatrzymać proces od wychodzenia czyli proces wyjdzie, gdy wszystkie wątki z IsBackground = false wysuną
Phil Devaney

66

Oto kolejna opcja:

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

1
jeśli utworzysz wątek w ten sposób, w jaki sposób zatrzymasz ten wątek z wątku interfejsu użytkownika?
slayernoah

1
@slayernoah możesz przekazać CancellationTokenSource jako parametr i ustawić token anulowania poza wątkiem: msdn.microsoft.com/en-us/library/dd997364(v=vs.110).aspx
Towarzysz Spongebob

7
Ten kod nie gwarantuje, że masz nowy wątek. Decyzja zależy od bieżącej implementacji używanego narzędzia ThreadPool. Zobacz jako przykład stackoverflow.com/questions/13570579/…
Giulio Caccin

3
@GiulioCaccin ThreadPool może wybrać istniejący wątek z puli, ale jest to zdecydowanie inny wątek.
Spongebob towarzysz

40

Spróbuj użyć klasy BackgroundWorker . Dajesz mu delegatów do uruchomienia i powiadamiania o zakończeniu pracy. Na stronie MSDN znajduje się przykład, z którym się łączyłem.


6
Z pewnością możesz to zrobić za pomocą klasy Thread, ale BackgroundWorker zapewnia metody uzupełniania wątków i raportowania postępów, które w innym przypadku byłyby konieczne, aby dowiedzieć się, jak samemu korzystać. Nie zapomnij, że musisz użyć Invoke, aby porozmawiać z interfejsem użytkownika!
Robert Rossney,

1
Ostrzegam: BackgroundWorker ma pewne subtelne ograniczenia, ale dla wspólnego „Chcę odejść i coś zrobić, a moja forma pozostanie responsywna”, jest to fantastyczne.
Merus

10
@Merus, czy mógłbyś rozwinąć te subtelne ograniczenia?
Christian Hudon,

14

Jeśli chcesz uzyskać wartość:

var someValue;

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

thread.Start();

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

6

Umieść ten kod w funkcji (kod, którego nie można wykonać w tym samym wątku co GUI), a aby uruchomić wykonanie tego kodu, wstaw następujące polecenie.

Thread myThread= new Thread(nameOfFunction);

workerThread.Start();

Wywołanie funkcji start na obiekcie wątku spowoduje wykonanie wywołania funkcji w nowym wątku.


3
@ user50612 O czym ty mówisz? Nowy wątek będzie działał nameOfFunctionw tle, a nie w bieżącym wątku GUI. IsBackgroundWłaściwość określa, czy wątek będzie utrzymywać aplikację żyje czy nie: msdn.microsoft.com/en-us/library/...
ZERO3

5

Tutaj, jak używać wątków z paskiem postępu, służy to po prostu zrozumieniu działania wątków, w formie są trzy paski postępu i przycisk 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 twój kod (powyżej) jest niepoprawny. Nie musisz obracać się, czekając na zakończenie. EndInvoke będzie blokować do momentu zasygnalizowania WaitHandle.

Jeśli chcesz zablokować do czasu zakończenia, po prostu musisz

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

lub alternatywnie

ar.AsyncWaitHandle.WaitOne();

Ale jaki jest sens wydawania połączeń anyc, jeśli blokujesz? Równie dobrze możesz użyć połączenia synchronicznego. Lepszym rozwiązaniem byłoby nie blokowanie i przekazywanie lambda do czyszczenia:

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

Należy pamiętać, że musisz wywołać EndInvoke. Wiele osób zapomina o tym i w końcu wyciekają WaitHandle, ponieważ większość implementacji asynchronicznych zwalnia waithandle w EndInvoke.


2

Jeśli zamierzasz użyć surowego obiektu Thread, musisz ustawić IsBackground na minimum, a także powinieneś ustawić model Threading Apartment (prawdopodobnie 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()
}

Poleciłbym klasę BackgroundWorker, jeśli potrzebujesz interakcji z interfejsem użytkownika.


Uważaj na ustawienie IsBackground na true. Prawdopodobnie nie działa tak, jak myślisz. To, co robi, to konfigurowanie, czy wątek zostanie zabity, gdy wszystkie wątki pierwszego planu zginą, czy też wątek utrzyma aplikację przy życiu. Jeśli nie chcesz, aby Twój wątek był przerywany w trakcie wykonywania, nie ustawiaj IsBackground na true.
Zero3


1

inna opcja, która wykorzystuje delegatów i pulę wątków ...

zakładając, że „GetEnergyUsage” jest metodą, która przyjmuje DateTime i inną DateTime jako argumenty wejściowe i zwraca 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, czy twój kod funkcjonalnie różni się od int usageCnt = nrgDel.Invoke(lastRunTime, procDT, null, null);? Wygląda na to, że i tak wstrzymuje bieżący wątek podczas snu ... Myślałem, że nic nie pomoże w zawieszeniu GUI, jeśli wywołasz go w wątku GUI
IgorK

Tak, BeginInvoke wywołuje delegata w innym wątku z puli wątków ... Jeśli używasz Invoke, delegat jest wywoływany synchronicznie w bieżącym wątku ... Ale masz rację, sen jest zły ... powinieneś to wyeliminować i zbierz wyniki za pomocą funkcji zwrotnej. Zmienię, żeby to pokazać
Charles Bretana,

1

Istnieje wiele sposobów uruchamiania osobnych wątków w .Net, z których każdy ma inne zachowania. Czy musisz kontynuować uruchamianie wątku po zamknięciu GUI? Czy potrzebujesz przekazać informacje między wątkiem a GUI? Czy wątek wymaga aktualizacji GUI? Czy wątek powinien wykonać jedno zadanie, a następnie zakończyć, czy powinien nadal działać? Odpowiedzi na te pytania podpowiedzą, jakiej metody użyć.

W witrynie Code Project znajduje się dobry artykuł na temat metody asynchronicznej, który opisuje różne metody i podaje przykładowy kod.

Uwaga: ten artykuł został napisany przed wprowadzeniem wzorca asynchronicznego / oczekiwania i biblioteki zadań równoległych do .NET.


Tego rodzaju informacji szukałem, ale twoja odpowiedź jest ofiarą gnicia linków.
Chris

Dzięki za informację, @Chris; link naprawiony.
Dour High Arch

0

Instrukcje: używanie wątku w tle do wyszukiwania plików

Musisz być bardzo ostrożny z dostępem z innych wątków do specyficznych dla GUI rzeczy (jest to powszechne w wielu zestawach narzędzi GUI). Jeśli chcesz zaktualizować coś w GUI z przetwarzania wątku, sprawdź tę odpowiedź, która moim zdaniem jest przydatna dla WinForm. W przypadku WPF zobacz to (pokazuje, jak dotknąć komponentu w metodzie UpdateProgress (), aby działał z innych wątków, ale tak naprawdę nie podoba mi się, że nie robi się to CheckAccess()przed BeginInvokeprzejściem przez Dispathcer, zobacz i wyszukaj w nim CheckAccess)

Szukałem konkretnej książki .NET na temat wątków i znalazłem (do pobrania za darmo). Więcej informacji na ten temat można znaleźć na stronie http://www.albahari.com/threading/ .

Wierzę, że znajdziesz to, czego potrzebujesz, aby uruchomić wykonywanie jako nowy wątek na pierwszych 20 stronach i ma on o wiele więcej (nie jestem pewien co do fragmentów specyficznych dla GUI, to znaczy ściśle specyficznych dla wątków). Z przyjemnością usłyszę, co społeczność myśli o tej pracy, ponieważ ją czytam. Na razie wyglądało to dla mnie całkiem fajnie (do pokazywania metod i typów specyficznych dla .NET dla wątków). Obejmuje również .NET 2.0 (a nie starożytny 1.1), co naprawdę doceniam.


Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.