Formのcloseでワーカースレッドを止める

id:carbuncle君の最近の日記(これとかこれとかこれとか)を見ていて、正直見てるだけじゃよく分からないので試しに適当なプログラムを作ってみた。最初はマインスイーパー・ソルバーでも作ろうかと思ったんだけど、途中で画面描画を作るのが面倒くさくなったので、極端に単純化したら何の変哲もない時計になった。この内容ならタイマーで十分なんだけど(実際タイマーを使ったサンプルをベースにしてる)、そこはまあテスト用のコードということで。

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Threading;

namespace MyClock
{
    public delegate Object Delegate(Object[] args);

    public class MainForm : System.Windows.Forms.Form
    {
        private MainRoutine routine;
        private Thread workerThread;
        private DateTime dt = DateTime.Now;

        public void setDateTime(DateTime dt) { this.dt = dt; }

        public MainForm()
        {
            routine = new MainRoutine(this);

            InitializeComponent();

            workerThread = new Thread(new ThreadStart(routine.Run));
            workerThread.Start();
        }

        private void InitializeComponent()
        {
            this.SuspendLayout();
            this.ClientSize = new Size(150, 70);
            this.FormBorderStyle = FormBorderStyle.FixedSingle;
            this.MaximizeBox = false;
            this.Name = "MyClock";
            this.Text = "C# Digital Clock";
            this.ResumeLayout(false);
        }

        static void Main()
        {
            Application.Run(new MainForm());
        }

        override protected void OnClosing(CancelEventArgs e)
        {
            Console.Write("OnClosing\n");

            if (routine == null)
                return;

            if (! routine.isTerminated()) {
                routine.shutdown();
                e.Cancel = true;
            }
        }

        override protected void OnClosed(EventArgs e)
        {
            Console.Write("OnClosed\n");

            workerThread.Join();
        }

        override protected void OnPaint(PaintEventArgs e)
        {
            Graphics g = e.Graphics;

            string str = dt.ToLongTimeString();
            Font font = new System.Drawing.Font("MS UI Gothic", 24F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(128)));
            Point pt = new System.Drawing.Point(7, 13);
            Brush brush = Brushes.Black;
            g.DrawString(str, font, brush, pt);
        }

        public void DelayClose()
        {
            AsyncCallback ac = new AsyncCallback(AsyncCallback);
            Delegate asyncCall = new Delegate(delegate(Object[] args) {
                    Console.Write("asyncCall called.\n");
                    Close();
                    return true;
                });
            asyncCall.BeginInvoke(new Object[0], ac, null);
        }

        public void AsyncCallback(IAsyncResult ar)
        {
            Console.Write("AsyncCallback called.\n");

            // EndInvoke で結果を取り出す。
            // しかし、EndInvoke は非同期メソッドのデリゲートに対してサポートされているので、
            // 非同期メソッドのデリゲートを知る必要がある。
            // そのために、System.Runtime.Remoting.Messaging.AsyncResult.AsyncDelegate プロパティから
            // 非同期メソッドのデリゲートを取得します。
            Delegate asyncCall =
                (Delegate) ((System.Runtime.Remoting.Messaging.AsyncResult) ar).AsyncDelegate;

            // 非同期メソッドのデリゲート asyncCall より、EndInvoke をコールすることにより、
            // パラメータ、返り値を得ることができる。
            asyncCall.EndInvoke(ar);
        }
    }

    public class MainRoutine
    {
        private MainForm frm;
        private bool running = true;
        private bool terminated = false;

        public bool isTerminated() { return terminated; }

        public MainRoutine(MainForm frm)
        {
            this.frm = frm;
        }

        public void Run()
        {
            while (running) {
                DateTime dt = DateTime.Now;

                Delegate invalidateDelegate = new Delegate(delegate(Object[] args) {
                        frm.setDateTime(dt);
                        // frm.Refresh();
                        frm.Invalidate();
                        return null;
                    });
                frm.Invoke(invalidateDelegate, new Object[] { new Object[0] });

                Thread.Sleep(1000);
            }

            terminated = true;

            Console.Write("terminated = true\n");

            Delegate closeDelegate = new Delegate(delegate(Object[] args) {
                    frm.DelayClose();
                    return null;
                });
            frm.Invoke(closeDelegate, new Object[] { new Object[0] });

            Console.Write("Thread terminated.\n");
        }

        public void shutdown()
        {
            running = false;
        }
    }
}

コンパイル手順&実行結果:

% mcs /r:System.Windows.Forms.dll /r:System.Drawing.dll /r:System.Data.dll clock.cs
% mono clock.exe
Mono System.Windows.Forms Assembly [$auto_build_revision$]
Keyboard: Japanese 106 keyboard layout
Gtk colorscheme read
-- ここで閉じるボタンを押す --
OnClosing
terminated = true
Thread terminated.
asyncCall called.
OnClosing
OnClosed
AsyncCallback called.

実はこの前買ったMonoの本(ISBN:4873112346)がどこかへ行ってしまったので正確な文法なんかは分からないんだけど、サンプルコードだけは探せば腐るほど有るので、それらを参考にしながら勘で書いた。Monoのバージョンが1.1なので書き方が古い部分もあると思うけど、新しいC#を使っている場合にはその辺は適当に読み替えて下さい。

ちなみに、
>RefleshをInvokeするのはどうも遅いようだ。(id:carbuncle:20060525#p2)
RefreshのかわりにInvalidateを使うと速くなるかも。