Bagaimana cara menunggu utas selesai dengan .NET?

178

Saya tidak pernah benar-benar menggunakan threading sebelumnya di C # di mana saya perlu memiliki dua utas, serta utas UI utama. Pada dasarnya, saya memiliki yang berikut ini.

public void StartTheActions()
{
  //Starting thread 1....
  Thread t1 = new Thread(new ThreadStart(action1));
  t1.Start();

  // Now, I want for the main thread (which is calling `StartTheActions` method) 
  // to wait for `t1` to finish. I've created an event in `action1` for this. 
  // The I wish `t2` to start...

  Thread t2 = new Thread(new ThreadStart(action2));
  t2.Start();
}

Jadi, pada dasarnya, pertanyaan saya adalah bagaimana agar utas menunggu yang lain selesai. Apa cara terbaik untuk melakukan ini?

Maxim Zaslavsky
sumber
4
Jika Anda telah "... tidak pernah menggunakan threading sebelumnya ..." Anda mungkin ingin mempertimbangkan beberapa alternatif sederhana seperti pola * Async ().
18аn
4
Jika Anda hanya menunggu utas 1 selesai, mengapa Anda tidak memanggil metode itu secara serempak?
Svish
13
Apa gunanya menggunakan utas saat Anda memproses secara linear?
John
1
@ John, sangat masuk akal bagi saya bahwa ada banyak kegunaan untuk memintal utas latar belakang yang berfungsi saat pengguna bekerja. Juga, bukankah pertanyaan Anda sama dengan yang sebelumnya?
user34660
Jawaban Rotem , menggunakan backgroundworker untuk penggunaan yang mudah, sangat sederhana.
Kamil

Jawaban:

264

Saya dapat melihat 5 opsi yang tersedia:

1. Utas. Gabung

Seperti jawaban Mitch. Tapi ini akan memblokir utas UI Anda, namun Anda mendapatkan Timeout bawaan untuk Anda.


2. Gunakan a WaitHandle

ManualResetEventadalah WaitHandleseperti yang disarankan jrista.

Satu hal yang perlu diperhatikan adalah jika Anda ingin menunggu beberapa utas, WaitHandle.WaitAll()tidak akan berfungsi secara default, karena membutuhkan utas MTA. Anda dapat menyiasatinya dengan menandai Main()metode Anda dengan MTAThread- namun ini memblokir pompa pesan Anda dan tidak direkomendasikan dari apa yang saya baca.


3. Menembak sebuah acara

Lihat halaman ini oleh Jon Skeet tentang acara dan multi-threading, ada kemungkinan bahwa suatu peristiwa dapat menjadi tidak bertuliskan antara ifdan EventName(this,EventArgs.Empty)- itu terjadi pada saya sebelumnya.

(Semoga kompilasi ini, saya belum mencoba)

public class Form1 : Form
{
    int _count;

    void ButtonClick(object sender, EventArgs e)
    {
        ThreadWorker worker = new ThreadWorker();
        worker.ThreadDone += HandleThreadDone;

        Thread thread1 = new Thread(worker.Run);
        thread1.Start();

        _count = 1;
    }

    void HandleThreadDone(object sender, EventArgs e)
    {
        // You should get the idea this is just an example
        if (_count == 1)
        {
            ThreadWorker worker = new ThreadWorker();
            worker.ThreadDone += HandleThreadDone;

            Thread thread2 = new Thread(worker.Run);
            thread2.Start();

            _count++;
        }
    }

    class ThreadWorker
    {
        public event EventHandler ThreadDone;

        public void Run()
        {
            // Do a task

            if (ThreadDone != null)
                ThreadDone(this, EventArgs.Empty);
        }
    }
}

4. Gunakan delegasi

public class Form1 : Form
{
    int _count;

    void ButtonClick(object sender, EventArgs e)
    {
        ThreadWorker worker = new ThreadWorker();

        Thread thread1 = new Thread(worker.Run);
        thread1.Start(HandleThreadDone);

        _count = 1;
    }

    void HandleThreadDone()
    {
        // As before - just a simple example
        if (_count == 1)
        {
            ThreadWorker worker = new ThreadWorker();

            Thread thread2 = new Thread(worker.Run);
            thread2.Start(HandleThreadDone);

            _count++;
        }
    }

    class ThreadWorker
    {
        // Switch to your favourite Action<T> or Func<T>
        public void Run(object state)
        {
            // Do a task

            Action completeAction = (Action)state;
            completeAction.Invoke();
        }
    }
}

Jika Anda menggunakan metode _count, mungkin ide (agar aman) untuk menambahkannya menggunakan

Interlocked.Increment(ref _count)

Saya tertarik untuk mengetahui perbedaan antara menggunakan delegasi dan acara untuk pemberitahuan utas, satu-satunya perbedaan yang saya tahu adalah acara disebut serempak.


5. Sebagai gantinya, lakukan secara asinkron

Jawaban atas pertanyaan ini memiliki deskripsi yang sangat jelas tentang opsi Anda dengan metode ini.


Delegasi / Acara di utas yang salah

Cara event / delegate melakukan sesuatu akan berarti metode event handler Anda ada di thread1 / thread2 bukan utas UI utama , jadi Anda harus beralih kembali tepat di atas metode HandleThreadDone:

// Delegate example
if (InvokeRequired)
{
    Invoke(new Action(HandleThreadDone));
    return;
}
Chris S
sumber
61

Menambahkan

t1.Join();    // Wait until thread t1 finishes

setelah Anda memulainya, tetapi itu tidak akan mencapai apa-apa karena pada dasarnya sama dengan menjalankan di utas utama!

Saya bisa sangat merekomendasikan membaca Joe Albahari's Threading di C # e-book gratis, jika Anda ingin mendapatkan pemahaman tentang threading di .NET.

Mitch Wheat
sumber
4
Meskipun Joinsecara harfiah apa yang diminta oleh si penanya, ini bisa sangat buruk secara umum. Panggilan ke Joinakan menutup utas dari mana ini dilakukan. Jika itu adalah utas GUI utama, ini BURUK ! Sebagai pengguna, saya secara aktif membenci aplikasi yang sepertinya berfungsi seperti ini. Jadi, silakan lihat semua jawaban lain untuk pertanyaan ini dan stackoverflow.com/questions/1221374/...
peSHIr
1
Saya setuju bahwa secara umum Gabung () buruk. Saya mungkin tidak membuat jawaban saya cukup jelas.
Mitch Wheat
2
Guys, satu ukuran tidak cocok untuk semua . Ada beberapa situasi, ketika seseorang benar-benar perlu memastikan, utas itu telah menyelesaikan pekerjaannya: pertimbangkan, utas itu memproses data, yang baru saja akan diubah. Dalam kasus seperti itu, memberi tahu utas untuk membatalkan dengan anggun dan menunggu sampai selesai (terutama, ketika satu langkah diproses dengan sangat cepat) adalah IMO sepenuhnya dibenarkan. Saya lebih suka mengatakan, bahwa Bergabung itu jahat (dalam istilah C ++ FAQ), yaitu. itu tidak akan digunakan kecuali benar-benar diperlukan.
Spook
5
Saya ingin menyatakan dengan jelas, bahwa Bergabung adalah alat, yang dapat berguna, meskipun faktanya, itu sangat sering disalahgunakan. Ada beberapa situasi, ketika itu hanya akan berfungsi tanpa efek samping yang tidak perlu (seperti menunda utas GUI untuk waktu yang cukup lama).
Spook
2
Bergabung adalah alat? Saya pikir Anda akan menemukan itu metode.
Mitch Wheat
32

Dua jawaban sebelumnya sangat bagus, dan akan bekerja untuk skenario sederhana. Namun, ada beberapa cara lain untuk menyinkronkan utas. Berikut ini juga akan berfungsi:

public void StartTheActions()
{
    ManualResetEvent syncEvent = new ManualResetEvent(false);

    Thread t1 = new Thread(
        () =>
        {
            // Do some work...
            syncEvent.Set();
        }
    );
    t1.Start();

    Thread t2 = new Thread(
        () =>
        {
            syncEvent.WaitOne();

            // Do some work...
        }
    );
    t2.Start();
}

ManualResetEvent adalah salah satu dari berbagai WaitHandle yang ditawarkan oleh .NET framework. Mereka dapat memberikan kemampuan sinkronisasi thread yang lebih kaya daripada alat yang sederhana namun sangat umum seperti lock () / Monitor, Thread.Join, dll. Mereka juga dapat digunakan untuk menyinkronkan lebih dari dua thread, memungkinkan skenario yang kompleks seperti thread 'master' yang mengoordinasikan banyak utas 'anak', beberapa proses bersamaan yang bergantung pada beberapa tahap satu sama lain untuk disinkronkan, dll.

jrista
sumber
30

Jika menggunakan dari .NET 4 sampel ini dapat membantu Anda:

class Program
{
    static void Main(string[] args)
    {
        Task task1 = Task.Factory.StartNew(() => doStuff());
        Task task2 = Task.Factory.StartNew(() => doStuff());
        Task task3 = Task.Factory.StartNew(() => doStuff());
        Task.WaitAll(task1, task2, task3);
        Console.WriteLine("All threads complete");
    }

    static void doStuff()
    {
        //do stuff here
    }
}

dari: https://stackoverflow.com/a/4190969/1676736

Sayed Abolfazl Fatemi
sumber
8
Anda tidak menyebutkan apa pun tentang Utas dalam jawaban Anda. Pertanyaannya adalah tentang Utas, bukan Tugas. Keduanya tidak sama.
Suamere
7
Saya datang dengan pertanyaan yang serupa (dan tingkat pengetahuan) ke poster asli dan jawaban ini sangat berharga bagi saya - tugas jauh lebih cocok untuk apa yang saya lakukan dan jika saya tidak menemukan jawaban ini saya akan menulis saya sendiri kolam thread yang mengerikan.
Chris Rae
1
@ ChrisRae, jadi ini seharusnya komentar di pertanyaan awal, bukan jawaban seperti ini.
Jaime Hablutzel
Karena ini tentang jawaban spesifik ini, saya pikir ini mungkin lebih masuk akal di sini.
Chris Rae
1
seperti yang dikatakan @Suamere, jawaban ini sama sekali tidak terkait dengan pertanyaan OP.
Glenn Slayden
4

Saya ingin utas utama Anda meneruskan metode panggilan balik ke utas pertama Anda, dan setelah selesai, ia akan memanggil metode panggil balik pada mainthread, yang dapat meluncurkan utas kedua. Ini menjaga agar utas utama Anda tidak menggantung saat menunggu Gabung atau Waithandle. Melewati metode sebagai delegasi adalah hal yang berguna untuk dipelajari dengan C #.

Ed Power
sumber
0

Coba ini:

List<Thread> myThreads = new List<Thread>();

foreach (Thread curThread in myThreads)
{
    curThread.Start();
}

foreach (Thread curThread in myThreads)
{
    curThread.Join();
}
Wendell Tagsip
sumber
0

Posting ke mungkin membantu beberapa orang lain, menghabiskan sedikit waktu mencari solusi seperti apa yang saya hasilkan. Jadi saya mengambil pendekatan yang sedikit berbeda. Ada opsi penghitung di atas, saya hanya menerapkannya sedikit berbeda. Saya memutar banyak thread dan menambah counter dan mengurangi counter ketika thread mulai dan berhenti. Kemudian dalam metode utama saya ingin berhenti dan menunggu utas untuk menyelesaikan saya lakukan.

while (threadCounter > 0)
{
    Thread.Sleep(500); //Make it pause for half second so that we don’t spin the cpu out of control.
}

Didokumentasikan di blog saya. http://www.adamthings.com/post/2012/07/11/ensure-threads-have-finished-before-method-continues-in-c/

Adam
sumber
4
Ini disebut sibuk menunggu. Ya itu bekerja dan kadang-kadang adalah solusi terbaik, tetapi Anda ingin menghindarinya jika mungkin karena itu membuang waktu CPU
MobileMon
@MobileMon, itu lebih merupakan pedoman daripada aturan. Dalam hal ini karena loop itu mungkin membuang 0,00000001% dari CPU dan OP sedang mengkode dalam C #, mengganti ini dengan sesuatu yang 'lebih efisien' akan membuang-buang waktu. Aturan optimasi pertama adalah - jangan. Ukur dulu.
Spike0xff
0

Ketika saya ingin UI dapat memperbarui tampilannya sambil menunggu tugas selesai, saya menggunakan loop sementara yang menguji IsAlive di utas:

    Thread t = new Thread(() => someMethod(parameters));
    t.Start();
    while (t.IsAlive)
    {
        Thread.Sleep(500);
        Application.DoEvents();
    }
Mark Emerson
sumber
-1

Berikut adalah contoh sederhana yang menunggu tapak selesai, dalam kelas yang sama. Itu juga membuat panggilan ke kelas lain di namespace yang sama. Saya menyertakan pernyataan "using" sehingga dapat dieksekusi sebagai Winform selama Anda membuat tombol1.

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;

namespace ClassCrossCall
{

 public partial class Form1 : Form
 {
  int number = 0; // this is an intentional problem, included for demonstration purposes
  public Form1()
  {
   InitializeComponent();
  }
  private void Form1_Load(object sender, EventArgs e)
  {
   button1.Text="Initialized";
  }
  private void button1_Click(object sender, EventArgs e)
  {
   button1.Text="Clicked";
   button1.Refresh();
   Thread.Sleep(400);
   List<Task> taskList = new List<Task>();
   taskList.Add(Task.Factory.StartNew(() => update_thread(2000)));
   taskList.Add(Task.Factory.StartNew(() => update_thread(4000)));
   Task.WaitAll(taskList.ToArray());
   worker.update_button(this,number);
  }
  public void update_thread(int ms)
  {
   // It's important to check the scope of all variables
   number=ms; // this could be either 2000 or 4000.  Race condition.
   Thread.Sleep(ms);
  }
 }

 class worker
 {
  public static void update_button(Form1 form, int number)
  {
   form.button1.Text=$"{number}";
  }
 }
}
pengguna3029478
sumber