スレッドの特定の処理が終わるまでメインスレッドをブロックして待機する

スレッドの特定の処理が終わるまでメインスレッドをブロックして待機するコードを紹介します。

概要

こちらの記事ではスレッドの終了までメインスレッドを待機するコードを紹介しました。一方で、スレッドの終了ではなく、スレッド中の特定の処理が終わるまでメインスレッドを待機させたい場合があります。特定の処理までメインスレッドを待機するには、System.ThreadingのManualResetEventクラスを用いて実現できます。

プログラム例

UI

下図のUIを作成します。
スレッドの特定の処理が終わるまでメインスレッドをブロックして待機する:画像1

コード

下記のコードを記述します。

FormMain.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;

namespace WaitThreadProcess
{
  public partial class FormMain : Form
  {
    public FormMain()
    {
      InitializeComponent();
    }

    private void Button1_Click(object sender, EventArgs e)
    {
      MyThread mt = new MyThread(); 
      Thread t = new Thread(new ThreadStart(mt.exec));
      t.Start();                                          
      mt.mre.WaitOne();

      System.IO.StreamReader sr = new System.IO.StreamReader("out1.txt");
      textBox1.Text += sr.ReadLine() + "\r\n";
      sr.Close();

      textBox1.Text += "---\r\n";

      t.Join();

      sr = new System.IO.StreamReader("out2.txt");
      textBox1.Text += sr.ReadLine() + "\r\n";
      sr.Close();

      textBox1.Text += "End\r\n";
    }
  }
}


MyThread.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace WaitThreadProcess
{
  class MyThread
  {
    public ManualResetEvent mre;

    public MyThread()
    {
      mre = new ManualResetEvent(false);
    }

    public void exec()
    {
      mre.Reset();

      System.Threading.Thread.Sleep(3000);

      System.IO.StreamWriter sw = new System.IO.StreamWriter("out1.txt");
      sw.WriteLine("最初の処理が終わりました。");
      sw.Close();

      mre.Set();

      System.Threading.Thread.Sleep(10000);

      sw = new System.IO.StreamWriter("out2.txt");
      sw.WriteLine("2番目の処理が終わりました。");
      sw.Close();

    }
  }
}

解説

サブスレッドのメンバに ManualResetEvent オブジェクトを宣言します。また、コンストラクタでManualResetEvent オブジェクトのインスタンスを作成します。コンストラクタにはfalseを設定し、シグナル状態にはしません。

public ManualResetEvent mre;

public MyThread()
{
  mre = new ManualResetEvent(false);
}


サブスレッド側の処理は下記のコードです。
最初の処理が終わったタイミングで、out1.txtファイルにメッセージを書き込み、その後、ManualResetEvent のインスタンスオブジェクトのSet()メソッドを呼び出します。Setメソッドの呼び出しによりManualResetEvent がシグナル状態に変わります。その後2番目の処理を実行します。2番目の処理の完了後にout2.txtファイルにメッセージを書き込み、スレッドを終了します。

public void exec()
{
  mre.Reset();

  System.Threading.Thread.Sleep(3000);

  System.IO.StreamWriter sw = new System.IO.StreamWriter("out1.txt");
  sw.WriteLine("最初の処理が終わりました。");
  sw.Close();

  mre.Set();

  System.Threading.Thread.Sleep(10000);

  sw = new System.IO.StreamWriter("out2.txt");
  sw.WriteLine("2番目の処理が終わりました。");
  sw.Close();
}


メインスレッドのコードは下記になります。最初にスレッドを作成しスレッドの処理を実行します。 スレッドの作成と実行についてはこちらの記事を参照してください。
スレッドの開始後、メインスレッドを待機させます。ManualResetEvent オブジェクトの WaitOne() メソッドを呼び出します。このメソッドにより、ManualResetEvent オブジェクトがシグナル状態になるまで待機状態になります。サブスレッドの1つ目の処理が完了し、Setメソッドが呼び出されたタイミングで、ManualResetEvent がシグナル状態になり、次行以降が実行されます。

テキストファイルを読み込みサブスレッド側で書き込まれたメッセージを表示します。その後、スレッドのJoinメソッドを呼び出し、スレッドの完了まで待機します。スレッドの完了後に再度テキストファイルを読み込み、サブスレッド側で書き込まれたメッセージを表示します。

private void Button1_Click(object sender, EventArgs e)
{
  MyThread mt = new MyThread(); 
  Thread t = new Thread(new ThreadStart(mt.exec));
  t.Start();                                          
  mt.mre.WaitOne();

  System.IO.StreamReader sr = new System.IO.StreamReader("out1.txt");
  textBox1.Text += sr.ReadLine() + "\r\n";
  sr.Close();

  textBox1.Text += "---\r\n";

  t.Join();

  sr = new System.IO.StreamReader("out2.txt");
  textBox1.Text += sr.ReadLine() + "\r\n";
  sr.Close();

  textBox1.Text += "End\r\n";
}

実行結果

上記のプロジェクトを実行します。下図のウィンドウが表示されます。
スレッドの特定の処理が終わるまでメインスレッドをブロックして待機する:画像2

[button1]をクリックします。テキストボックスが下図の表示となります。ボタン押下後3秒ほど経過後に、「最初の処理が終わりました。」のメッセージが表示され、その後10秒後に「2番目の処理が終わりました。」のメッセージが表示されます。処理の完了までメインスレッドが待機できていることが確認できます。

スレッドの特定の処理が終わるまでメインスレッドをブロックして待機する:画像3

補足

この方法で、スレッドの特定の処理が終わるまで待機できますが、メインスレッドの待機中はUIの操作がロックされてしまうため、長時間の待機する実装はあまり適切ではありません。長い時間がかかる場合はスレッド側からメインスレッドの処理を呼び出すか、非同期で終了を待つ実装のほうが適切です。

AuthorPortraitAlt
著者
iPentecのメインプログラマー
C#, ASP.NET の開発がメイン、少し前まではDelphiを愛用
作成日: 2010-05-28
Copyright © 1995–2025 iPentec all rights reserverd.