スレッドが.NETで完了するのを待つ方法は?


178

メインのUIスレッドだけでなく、2つのスレッドが必要なC#では、これまでスレッドを使用したことがありません。基本的に、私は以下を持っています。

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

だから、本質的に、私の質問は、スレッドが終了するのを待つ方法です。これを行う最良の方法は何ですか?


4
「...の前にスレッドを使用したことがない...」の場合は、* Async()パターンなど、より簡単な代替手段を検討することをお勧めします。
2009年

4
とにかくスレッド1が完了するのを待っているだけなら、なぜそのメソッドを同期的に呼び出すだけではないのですか?
2010

13
直線的に処理しているときにスレッドを使用する意味は何ですか?
ジョン

1
@John、ユーザーが作業しているときに機能するバックグラウンドスレッドをスピンオフする多くの用途があることは、私には完全に理にかなっています。また、あなたの質問は前の質問と同じではありませんか?
user34660 2017年

Rotemの答えは、簡単に使用できるようにbackgroundworkerを使用しており、非常にシンプルです。
カミル

回答:


264

私は利用可能な5つのオプションを見ることができます:

1. Thread.Join

ミッチの答えと同じです。しかし、これによりUIスレッドがブロックされますが、タイムアウトが組み込まれます。


2.を使用 WaitHandle

ManualResetEventWaitHandlejristaが提案するとおりです。

WaitHandle.WaitAll()MTAスレッドが必要なため、複数のスレッドを待機したい場合、デフォルトでは機能しないことに注意してください。Main()メソッドにマークを付けることでこれを回避できますが、これMTAThreadはメッセージポンプをブロックするため、私が読んだことからはお勧めできません。


3.イベントを発生させる

イベントとマルチスレッドについてJon Skeetによるこのページを参照してください。イベントは、ifとの間でサブスクライブ解除される可能性がありますEventName(this,EventArgs.Empty)-以前に発生しました。

(うまくいけば、これらはコンパイルされ、私は試していません)

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.デリゲートを使用する

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メソッドを使用する場合は、(安全のため)を使用してインクリメントすることをお勧めします

Interlocked.Increment(ref _count)

スレッド通知にデリゲートとイベントを使用する違いを知りたいのですが、イベントが同期的に呼び出されることだけが違います。


5.代わりに非同期で行う

この質問に対する答えには、この方法でのオプションの非常に明確な説明があります。


間違ったスレッドのデリゲート/イベント

イベント/デリゲートの方法は、イベントハンドラーメソッドメインのUIスレッドはなく thread1 / thread2にあることを意味します。そのため、HandleThreadDoneメソッドの最上部で元に戻す必要があります。

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

61

追加

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

あなたがそれを開始した後、それは本質的にメインスレッドで実行するのと同じ結果なので、それはあまり達成されません!

.NETでのスレッド化について理解を深めたい場合は、Joe AlbahariのThreading in C# free e-bookを読むことを強くお勧めします。


4
それJoinは文字通り、質問者が求めていたように見えますが、これは一般的に非常に悪い場合があります。への呼び出しは、Joinこれが行われているスレッドをハングアップします。それがたまたまメインGUIスレッドであるなら、これは悪いです!ユーザーとして、私はこのように動作するように見えるアプリケーションを積極的に嫌っています。この質問に対する他のすべての回答とstackoverflow.com/questions/1221374/…
peSHIr

1
一般に、Join()が悪いことに同意します。私の答えではおそらくそれを十分に明らかにしていませんでした。
ミッチウィート

2
みんな、ワンサイズはすべてに合うわけではありません。本当に確認する必要がある状況で、そのスレッドが作業を完了した場合があります。スレッドがデータを処理することを検討してください。これは、変更されるところです。そのような場合、スレッドに正常にキャンセルするよう通知し、スレッドが終了するまで(特に、1つのステップが非常に速く処理されるとき)待機することは、IMOで完全に正当化されます。むしろ、Joinは(C ++ FAQ用語では)であると言います。本当に必要な場合以外使用しないでください。
幽霊

5
私は明確に述べたいのですが、Joinはツールであり、非常に頻繁に誤用されているにもかかわらず、便利なツールです。不要な副作用なしに機能する場合があります(メインGUIスレッドをかなりの時間停止させるなど)。
幽霊

2
参加はツールですか?方法だとわかると思います。
ミッチウィート

32

前の2つの答えはすばらしいもので、単純なシナリオで機能します。ただし、スレッドを同期する方法は他にもあります。以下も機能します:

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フレームワークが提供する必要があるさまざまなWaitHandleの1つです。それらは、lock()/ Monitor、Thread.Joinなどのシンプルで非常に一般的なツールよりもはるかに豊富なスレッド同期機能を提供できます。また、2つ以上のスレッドを同期するために使用でき、「マスター」スレッドなどの複雑なシナリオを可能にします複数の「子」スレッド、同期するために互いにいくつかの段階に依存する複数の同時プロセスなどを調整します。


30

.NET 4から使用する場合、このサンプルは次のことに役立ちます。

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

から:https : //stackoverflow.com/a/4190969/1676736


8
あなたはあなたの答えでスレッドについて何も言及しませんでした。問題はタスクではなくスレッドについてです。2つは同じではありません。
Suamere 2017

7
私は元のポスターと同様の質問(および知識レベル)を用意しましたが、この回答は私にとって非常に価値がありました-私がしていることにはタスクがより適切であり、この回答が見つからなかった場合は、自分のひどいスレッドプール。
Chris Rae、

1
@ChrisRaeなので、これは元の質問のコメントであり、このような回答ではありません。
Jaime Hablutzel、

この特定の回答についてですので、おそらくここではもっと理にかなっていると思います。
Chris Rae

1
@Suamereが言ったように、この答えはOPの質問とはまったく無関係です。
Glenn Slayden


4

私はあなたのメインスレッドにコールバックメソッドを最初のスレッドに渡し、それが完了すると、メインスレッドのコールバックメソッドが呼び出され、2番目のスレッドを起動できます。これにより、メインスレッドがJoinまたはWaithandleを待機している間にハングするのを防ぎます。メソッドをデリゲートとして渡すことは、とにかくC#で学ぶのに役立ちます。


0

これを試して:

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

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

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

0

おそらく他の人を助けるために投稿して、私が思いついたような解決策を探すためにかなりの時間を費やしました。だから私は少し違うアプローチを取った。上記のカウンターオプションがありますが、私は少し異なる方法で適用しました。私は多数のスレッドをスピンオフしていて、スレッドが開始および停止したときにカウンターをインクリメントし、カウンターをデクリメントしました。次に、メインメソッドで一時停止し、スレッドが完了するのを待っていました。

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

私のブログに記載されています。 http://www.adamthings.com/post/2012/07/11/ensure-threads-have-finished-before-method-continues-in-c/


4
これはビジー待機と呼ばれます。はい、それは機能し、時には最善の解決策ですが、CPU時間を浪費するため、可能であればそれを避けたいと思います
MobileMon

@MobileMonは、ルールというよりもガイドラインです。この場合、そのループはCPUの0.00000001%を浪費する可能性があり、OPはC#でコーディングしているため、これを「より効率的な」ものに置き換えると、完全に時間の無駄になります。最適化の最初のルールは-しないでください。最初に測定します。
Spike0xff

0

タスクが完了するのを待っている間にUIが表示を更新できるようにしたい場合は、スレッドでIsAliveをテストするwhileループを使用します。

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

-1

同じクラス内で、トレッドが終了するのを待つ単純な例を次に示します。また、同じ名前空間内の別のクラスを呼び出します。「using」ステートメントを含めたので、button1を作成する限り、Winformとして実行できます。

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}";
  }
 }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.