【備忘録】別スレッドからメインスレッドのフォームを操作する


はじめに

明日の自分のためにメモっとく。

ポイントは、別スレッドでSystem.Windows.Forms.Controlを受け取り、Invokeを使ってイベントを通知すること。

環境

  • Windows7 Professional SP1
  • Microsoft Visual Studio Version 12.0.31101.00 Update 4
  • Microsoft .NET Framework Version 4.5.51209
  • Microsoft Visual C# 2013
  • NLog 3.2.0.0

Example

Form1.cs
    /// <summary>
    /// 【備忘録】別スレッドからメインスレッドのフォームを操作する
    /// </summary>
    public partial class Form1 : Form
    {
        private NLog.Logger logger = NLog.LogManager.GetLogger("fooLogger");
        private bool isSubThreadAllFinished = false;
        private int runSubThreads;
        private int endSubThreads;

        public Form1()
        {
            InitializeComponent();
        }

        /// <summary>
        /// 別スレッドからフォームのテキストボックスへ値を入れる
        /// </summary>
        /// <param name="sender">呼び元オブジェクト</param>
        /// <param name="e">イベントデータ</param>
        private async void button1_Click(object sender, EventArgs e)
        {
            WorkerClass wc = null;
            int minThreads = 10;

            try
            {
                isSubThreadAllFinished = false;
                runSubThreads = 0;

                wc = new WorkerClass(logger, this);
                wc.OnSubThreadRunning += Form1_SubThreadRunning;
                wc.OnSubThreadFinished += Form1_SubThreadFinished;

                // スレッドプールスレッドが実行するメソッドを設定
                WaitCallback waitCallback = new WaitCallback(wc.Execute);

                // スレッドプールスレッドの最小数を変更
                ThreadPool.SetMinThreads(minThreads, minThreads);

                // 別スレッドを作る
                for (int i = 0; i < minThreads; i++)
                {
                    // メソッドをスレッドプールキューに登録
                    ThreadPool.QueueUserWorkItem(waitCallback, 1000 * i);
                    runSubThreads++;
                }

                // 別メソッドの終了数が実行した別メソッドの総数になったらループを抜ける
                await Task.Run(() => { 
                    while (!isSubThreadAllFinished)
                    {
                        logger.Info("終了まで待機中...");
                    }
                });
            }
            catch (Exception ex)
            {
                logger.Error(ex);
            }
            finally
            {
                this.textBox1.Text = "別スレッドは全て終了";

                if (wc != null)
                {
                    wc = null;
                }
            }

        }

        /// <summary>
        /// 別スレッドが実行された時に呼ばれるイベントメソッド
        /// </summary>
        /// <param name="sender">呼び元オブジェクト</param>
        /// <param name="e">ユーザ定義イベントデータ</param>
        void Form1_SubThreadRunning(object sender, FooEventArgs e)
        {
            this.textBox1.Text = e.Message;
        }

        /// <summary>
        /// 別スレッドが終了するたびに呼ばれるイベントメソッド
        /// </summary>
        /// <param name="sender">呼び元オブジェクト</param>
        /// <param name="e">ユーザ定義イベントデータ</param>
        private void Form1_SubThreadFinished(object sender, EventArgs e)
        {
            // 終了した別メソッドをカウント
            endSubThreads++;
            // 別メソッドの終了数が実行した別メソッドの総数になったか判定
            isSubThreadAllFinished = (runSubThreads <= endSubThreads);
        }
    }
WorkerClass.cs
    /// <summary>
    /// 別スレッドで実行するクラス
    /// </summary>
    class WorkerClass
    {
        private NLog.Logger logger;
        private Control mainThreadForm;

        /// <summary>
        /// 別スレッドの実行通知用デリゲート(イベントハンドラ)
        /// </summary>
        /// <param name="sender">呼び元オブジェクト</param>
        /// <param name="e">ユーザ定義イベントデータ</param>
        public delegate void SubThreadRunningEventHandler(object sender, FooEventArgs e);
        /// <summary>
        /// 別スレッドの実行通知用イベント
        /// </summary>
        public event SubThreadRunningEventHandler OnSubThreadRunning;
        /// <summary>
        /// 別スレッドの終了通知用イベント
        /// </summary>
        public event EventHandler OnSubThreadFinished;

        /// <summary>
        /// カスタムコンストラクタ
        /// </summary>
        /// <param name="aLogger">NLog</param>
        /// <param name="aMainThreadForm">メインスレッドのフォーム</param>
        public WorkerClass(NLog.Logger aLogger, Control aMainThreadForm)
        {
            logger = aLogger;
            mainThreadForm = aMainThreadForm;
        }

        /// <summary>
        /// 別スレッドの実行メソッド
        /// </summary>
        /// <param name="state">パラメータ</param>
        public void Execute(object state)
        {
            try
            {
                // 渡されたパラメータの値だけ待機する
                Thread.Sleep((int)state);

                // Invoke()を使って実行を通知する
                FooEventArgs e = new FooEventArgs();
                e.Message = state.ToString();
                mainThreadForm.Invoke(OnSubThreadRunning, new object[] { this, e });
            }
            finally
            {
                // Invoke()を使って終了を通知する
                mainThreadForm.Invoke(OnSubThreadFinished, new object[] { this, EventArgs.Empty });
            }
        }
    }
FooEventArgs.cs
    /// <summary>
    /// ユーザ定義イベントデータクラス
    /// </summary>
    class FooEventArgs : EventArgs
    {
        /// <summary>文字列をもたせる</summary>
        public string Message;
    }