İş parçacığının .NET ile bitmesini nasıl bekleyebilirim?


178

Daha önce iki iş parçacığı yanı sıra ana UI iş parçacığı olması gereken C # daha önce iş parçacığı hiç kullanmadım. Temel olarak, aşağıdakilere sahibim.

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

Yani, aslında, sorum başka bir işin bitmesini beklemek nasıl bir iplik var. Bunu yapmanın en iyi yolu nedir?


4
"... daha önce iş parçacığını daha önce hiç kullanmadıysanız ..."
05'da

4
Sadece 1 iş parçacığının yine de bitmesini bekliyorsanız, neden sadece bu yöntemi eşzamanlı olarak çağırmıyorsunuz?
Svish

13
Doğrusal bir şekilde işlerken iş parçacığı kullanmanın anlamı nedir?
John

1
@John, kullanıcı çalışırken çalışan bir arka plan iş parçacığını döndürmek için birçok kullanım olduğunu bana mantıklı geliyor. Ayrıca, sorunuz bir öncekiyle aynı değil mi?
user34660

Rotem'in cevabı , kolay kullanım için arka plan çalışanı kullanarak, çok basit.
Kamil

Yanıtlar:


264

Mevcut 5 seçeneği görebiliyorum:

1. Konu.Katıl

Mitch'in cevabı gibi. Ancak bu UI iş parçacığınızı engelleyecektir, ancak sizin için yerleşik bir Zaman Aşımı alırsınız.


2. Bir WaitHandle

ManualResetEvent bir WaitHandle jrista önerdiği gibi.

Dikkat edilmesi gereken bir şey WaitHandle.WaitAll(), bir MTA iş parçacığı gerektirdiğinden, birden fazla iş parçacığını beklemek istiyorsanız, varsayılan olarak çalışmaz. Main()Yönteminizi işaretleyerek bu sorunu çözebilirsiniz MTAThread- ancak bu mesaj pompanızı engeller ve okuduğumdan önerilmez.


3. Bir olayı başlat

Jon Skeet tarafından etkinlikler ve çoklu iş parçacığı hakkındaki bu sayfaya bakın , bir etkinliğin ifveEventName(this,EventArgs.Empty) - .

(Umarım bunlar derlenir, denemedim)

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. Bir temsilci kullanın

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

_Count yöntemini kullanırsanız, kullanarak güvenli hale getirmek bir fikir olabilir (güvenli olmak için)

Interlocked.Increment(ref _count)

Ben iş parçacığı bildirimi için delege ve olayları kullanma arasındaki farkı bilmek istiyorum, biliyorum tek fark olaylar senkronize olarak adlandırılır.


5. Bunun yerine eşzamansız olarak yapın

Cevabı bu soruya bu yöntem ile seçeneklerin çok net bir açıklaması vardır.


Yanlış iş parçacığında Temsilci / Etkinlikler

Bir şeyler yapmanın olay / temsilci yolu, olay işleyici yönteminizin ana kullanıcı arabirimi iş parçacığında değil, iş parçacığı1 / iş parçacığı2'de olduğu anlamına gelir;

// Delegate example
if (InvokeRequired)
{
    Invoke(new Action(HandleThreadDone));
    return;
}

61

Ekle

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

başlattıktan sonra, ancak ana iş parçacığı üzerinde çalışanla aynı sonucu elde edemeyeceği kadar başarılı olmaz!

.NET'te iş parçacığı hakkında bir fikir edinmek istiyorsanız, Joe Albahari'nin C # ücretsiz e-kitapta iş parçacığını okumanızı şiddetle tavsiye ederim .


4
Rağmen Joinbu genel olarak son derece kötü olabilir, için asker soran edilmiş göründüğü gibi tam anlamıyla. Bunun Joiniçin yapılan bir ileti dizisi telefonu kapatır. Bu ana GUI iş parçacığı olur, bu KÖTÜ ! Bir kullanıcı olarak, bu şekilde çalışıyor gibi görünen uygulamaları aktif olarak tiksiniyorum. Bu yüzden lütfen bu soruya verilen diğer tüm cevaplara bakın ve stackoverflow.com/questions/1221374/…
peSHIr

1
Genel olarak Join () işlevinin kötü olduğunu kabul ediyorum. Belki cevabımda bunu yeterince açıklığa kavuşturmadım.
Mitch Wheat

2
Çocuklar, bir beden herkese uymuyor . Gerçekten iş parçacığının işini bitirdiğinden emin olunması gereken durumlar vardır: iş parçacığının değiştirilmek üzere olan verileri işlediğini düşünün. Bu durumda, ipliğin zarif bir şekilde iptal edileceğini ve bitinceye kadar beklemesini bildirmek (özellikle bir adım çok hızlı bir şekilde işlendiğinde) IMO tamamen haklıdır. Ben, Join (C ++ SSS açısından), yani kötü olduğunu söyleyebilirim . gerçekten gerekli olmadıkça kullanılmayacaktır .
Spook

5
Açıkça belirtmek isterim ki, Katıl, çoğu zaman yanlış kullanıldığına rağmen faydalı olabilecek bir araç. Sadece gereksiz yan etkiler olmadan çalışacağı bazı durumlar vardır (ana GUI ipliğini dikkat çekici bir süre kesmek gibi).
Spook

2
Katılmak bir araçtır? Bence bu bir yöntem.
Mitch Wheat

32

Önceki iki cevap harika ve basit senaryolar için işe yarayacak. Bununla birlikte, evreleri senkronize etmenin başka yolları da vardır. Aşağıdakiler de işe yarayacaktır:

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 , .NET çerçevesinin sunduğu çeşitli WaitHandle'lerden biridir . Lock () / Monitor, Thread.Join, vb.Gibi basit ama çok yaygın araçlardan çok daha zengin iş parçacığı senkronizasyon yetenekleri sağlayabilirler. İkiden fazla iş parçacığını eşitlemek için kullanılabilirler ve 'ana' iş parçacığı gibi karmaşık senaryolara izin verir birden çok 'alt' iş parçacığını, senkronize edilecek birbirinin birkaç aşamasına bağlı olan birden çok eşzamanlı işlemi koordine eden vb.


30

.NET 4'ten kullanıyorsanız bu örnek size yardımcı olabilir:

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
    }
}

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


8
Cevabınızda Threads hakkında hiçbir şey söylemediniz. Soru Görevler ile değil, Konular ile ilgilidir. İki aynı değildir.
Suamere

7
Orijinal postere benzer bir soru (ve bilgi düzeyi) ile geldim ve bu cevap benim için çok değerliydi - görevler yaptığım şey için çok daha uygun ve bu cevabı bu olmasaydım yazmıştım kendi korkunç iplik havuzu.
Chris Rae

1
@ChrisRae, yani bu orijinal soruya bir yorum olmalı, böyle bir cevap değil.
Jaime Hablutzel

Bu özel cevapla ilgili olarak bence burada muhtemelen daha mantıklı.
Chris Rae

1
@Suamere'nin dediği gibi, bu cevap OP sorusuyla tamamen ilgisizdir.
Glenn Slayden


4

Ana iş parçacığının ilk iş parçacığınıza geri arama yöntemini geçirmesini isterdim ve bittiğinde, ikinci iş parçacığını başlatabilen mainthread üzerinde geri arama yöntemini çağırır. Bu, birleştirme veya Waithandle beklerken ana ipliğin asılı kalmasını önler. Metodları delege olarak geçirmek zaten C # ile öğrenmek için faydalı bir şeydir.


0

Bunu dene:

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

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

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

0

Belki başkalarına yardım etmek için yayınlamak, ortaya çıkardığım gibi bir çözüm aramak için biraz zaman harcadı. Bu yüzden biraz farklı bir yaklaşım izledim. Yukarıda bir sayaç seçeneği var, ben biraz farklı uyguladım. Çok sayıda iş parçacığını döndürüyordum ve bir sayacı artırdım ve bir iş parçacığı başlatılıp durduğunda bir sayacı azalttım. Sonra ana yöntemde duraklatmak istedim ve iş parçacıklarının tamamlanmasını bekledim.

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

Blogumda belgelendi. http://www.adamthings.com/post/2012/07/11/ensure-threads-have-finished-before-method-continues-in-c/


4
Buna meşgul bekleme denir. Evet çalışır ve bazen en iyi çözümdür, ancak mümkünse bundan kaçınmak istersiniz çünkü CPU zamanını harcar
MobileMon

@MobileMon bu bir kuraldan çok bir kılavuzdur. Bu durumda, bu döngü CPU'nun% 0.00000001'ini boşa harcayabileceği ve OP'nin C # 'da kodladığı için, bunu' daha verimli 'bir şeyle değiştirmek tam bir zaman kaybı olacaktır. Optimizasyonun ilk kuralı - yapma. Önce ölçün.
Spike0xff

0

UI'nin bir görevin tamamlanmasını beklerken ekranını güncelleyebilmesini istediğimde, iş parçacığında IsAlive'yi test eden bir while döngüsü kullanıyorum:

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

-1

İşte aynı sınıfta bir lastik sırtının bitmesini bekleyen basit bir örnek. Aynı ad alanındaki başka bir sınıfa da çağrı yapar. Ben button1 oluşturmak sürece bir Winform yürütmek böylece "using" ifadeleri dahil.

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}";
  }
 }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.