C# 入門 & 実践 / C sharp

501-5. Monitorで同期管理


まずは、前回と同じで
lock ()
Interlocked
を使用せずに
Moniter
を利用した形に書き換えます。

        private void counter(object sleep_o )
        {
            int sleep_i = (int)sleep_o;

            for (int i = 0; i < 10; i++)
            {
                    // モニター開始!
                    Monitor.Enter(this);
                    try
                    {

                        Thread.Sleep(sleep_i);
                        count++;
                        res += Thread.CurrentThread.Name + " : " + count + "\r\n";
                    }
                    finally
                    {
                        Monitor.Exit(this);
                    }
                
            }
        }

こうすると501-4と同じ結果が得られます。

T1 counter : 1
T2 counter : 2
T1 counter : 3
T1 counter : 4
T2 counter : 5
T1 counter : 6
T2 counter : 7
T1 counter : 8
T1 counter : 9
T2 counter : 10
T1 counter : 11
T1 counter : 12
T2 counter : 13
T1 counter : 14
T1 counter : 15
T2 counter : 16
T2 counter : 17
T2 counter : 18
T2 counter : 19
T2 counter : 20

次は、waitを入れてみます。
wait で中断したら、 pulse で通知が来ないと復帰できないというリスク?があります・・・

Pulse で通知して、 Exit で終了した瞬間、waitで待っているのもがスタートします。

T3 を待ち状態にしておき、 count が6になったら通知します。
        // ここでテストしてみます。
        private void goThread()
        {
            Thread t1 = new Thread(new ParameterizedThreadStart(counter));
            t1.Name = "T1 counter";
            
            Thread t3 = new Thread(new ThreadStart(try_wait));
            t3.Name = "T3 trywait";

            // T2 を先に開始して待ちます。
            t1.Start(1);
            t3.Start();
            // 終わるまで待つ。
            t1.Join();
            t3.Join();

            this.textBox1.Text = res;


        }
        private void counter(object sleep_o )
        {
            int sleep_i = (int)sleep_o;

            
                
            for (int i = 0; i < 10; i++)
            {
                    // モニター開始!
                    Monitor.Enter(this);
                    try
                    {

                        Thread.Sleep(sleep_i);
                        count++;
                        res += Thread.CurrentThread.Name + " : " + count + "\r\n";

                        if (count == 6)
                        {
                            // 6 になったら通知してみます!
                            // 他スレッドへ通知
                            Monitor.Pulse(this);
                            //Thread.Sleep(50);
                        }
                    }
                    finally
                    {
                        Monitor.Exit(this);
                    }
                
            }
        }
        // 待ちを入れてみる。
        private void try_wait()
        {
            Monitor.Enter(this);
            try
            {
                res += Thread.CurrentThread.Name + " : 待ちます!\r\n";
                Monitor.Wait(this);
                res += Thread.CurrentThread.Name + " : 通知キタ!\r\n";
            }
            finally 
            {
                Monitor.Exit(this);
            }
        }

結果は
T1 counter : 1
T1 counter : 2
T3 trywait : 待ちます!
T1 counter : 3
T1 counter : 4
T1 counter : 5
T1 counter : 6
T1 counter : 7
T3 trywait : 通知キタ!
T1 counter : 8
T1 counter : 9
T1 counter : 10

こうなりました。
6の後に来ていないのは、通知を受けるより早く、7が始まってしまったと言うことでしょう。

試しにPulseをコメントにしてみると・・・終了しません・・・
これどうしましょう?

そんなときは、時間指定をしてタイムアウトしたらエラーにしましょう。
Pulseはコメントにして、
T1 は 10 ミリ秒ごとに設定
T3 のタイムアウトは、30 ミリ秒に設定

        // 待ちを入れてみる。
        private void try_wait()
        {
            Monitor.Enter(this);
            try
            {
                res += Thread.CurrentThread.Name + " : 待ちます!\r\n";
                bool wait_ret = Monitor.Wait(this, 30);

                count++;
                if (wait_ret)
                {
                    res += Thread.CurrentThread.Name + " : " + count + "通知キタ!\r\n";
                }
                else
                {
                    res += Thread.CurrentThread.Name + " : " + count + ">。<;アウト!\r\n";
                }
            }
            finally 
            {
                Monitor.Exit(this);
            }
        }
T1 counter : 1
T3 trywait : 待ちます!
T1 counter : 2
T1 counter : 3
T1 counter : 4
T3 trywait : 5>。<;アウト!
T1 counter : 6
T1 counter : 7
T1 counter : 8
T1 counter : 9
T1 counter : 10

こんな感じで、完璧ですね。Pulse が来たら true を返します。

これを駆使してマルチスレッドをがんがん使いましょう。
ファイル書き込みや通信、DB処理何にでも使えますね。
データクラスを作成して、更新は全てそこを通すと言う感じで作成したら良いようです。

Monitor の問題点
finally で Monitor.Exit(this)
で終わりにしていましたが、例外が出た場合、駄目ですね

try{
// 0
}catch(Exception e){
// 1
 throw e;
}finally{
// 2
Monitor.Exit(this);
}

こうすると例外が発生すると2にいかなくなるので

try{
// 0
Monitor.Exit(this);
}catch(Exception e){
// 1
Monitor.Exit(this);
 throw e;
}

こうしないといけないようです!気をつけましょう。



501-4. ParameterizedThreadStart でパラメータを渡す! « マルチスレッド同期 » 501-6. スレッドセーフな呼び出し!


C# 入門 & 実践 / C sharp