C#で作るWindowsアプリ
−スレッドを使う−


[トップ] [目次] [←前]

スレッドを使う

ちと強引ですがHTTP GETする部分を別スレッドにしてみます。
今回もコードから


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142

namespace Sample6a {
    using System;
    using System.IO;
    using System.Net;
    using System.Drawing;
    using System.Threading;
    using System.Windows.Forms;
    
    public class HttpThread {
        public delegate void HttpFinishDelegate(string s);
        
        private HttpFinishDelegate mHttpFinishDelegate;
        
        // コンストラクタ
        public HttpThread(HttpFinishDelegate d) {
            mHttpFinishDelegate = d;
            Thread th = new Thread(new ThreadStart(this.Run));
            th.Start();
        }
        
        // スレッド関数
        private void Run() {
            WebRequest req = WebRequest.Create("http://www.msn.co.jp/home.htm");
            WebResponse rsp = req.GetResponse();
            Stream stm = rsp.GetResponseStream();
            if (stm != null) {
                StreamReader reader = new StreamReader(stm, System.Text.Encoding.GetEncoding("Shift_JIS"));
                string s = reader.ReadToEnd();
                Form frm = mHttpFinishDelegate.Target as Form;
                if (frm != null) {
                    frm.Invoke(mHttpFinishDelegate, new object[] { s });
                }
                stm.Close();
            }
            rsp.Close();
        }
    }
    
    public class MyForm : Form {
        private TextBox mTextBox;               // テキストボックス
        private string mDefaultDirectory;      // カレントディレクトリ
        
        // Main
        public static void Main(string[] args) {
            MyForm frm = new MyForm();
            Application.Run(frm);
        }
        
        // コンストラクタ
        public MyForm() {
            // メインメニューを生成
            MainMenu mm = new MainMenu();
            
            // ファイルメニューを生成
            MenuItem mi = mm.MenuItems.Add("ファイル(&F)");
            mi.MenuItems.Add(new MenuItem("開く(&O)...", new EventHandler(this.FileOpen_Clicked)));
            mi.MenuItems.Add("-");
            mi.MenuItems.Add(new MenuItem("終了(&X)", new EventHandler(this.FileExit_Clicked), Shortcut.CtrlQ));
            
            // ヘルプメニューを生成
            mi = mm.MenuItems.Add("ヘルプ(&H)");
            mi.MenuItems.Add(new MenuItem("バージョン情報(&A)...", new EventHandler(this.HelpAbout_Clicked)));
            
            // フォームのメニューとしてセット
            this.Menu = mm;
            
            // 開くボタンを生成
            Button btn = new Button();
            btn.Text = "開く...";
            btn.Location = new Point(2, 2);    // 座標を設定
            btn.Size = new Size(100, 22);      // サイズを設定
            btn.TabIndex = 0;                  // タブ順は最初
            btn.Anchor = AnchorStyles.Top | AnchorStyles.Left;  // 左上を固定
            btn.Click += new EventHandler(this.FileOpen_Clicked);
            this.Controls.Add(btn);
            
            // URLを開くボタンを生成
            btn = new Button();
            btn.Text = "URLを開く...";
            btn.Location = new Point(104, 2);  // 座標を設定
            btn.Size = new Size(100, 22);      // サイズを設定
            btn.TabIndex = 1;                  // タブ順は最初
            btn.Anchor = AnchorStyles.Top | AnchorStyles.Left;  // 左上を固定
            btn.Click += new EventHandler(this.UrlOpen_Clicked);
            this.Controls.Add(btn);
            
            // テキストボックスを生成
            mTextBox = new TextBox();
            mTextBox.Text = "";
            mTextBox.Location = new Point(0, 26);   // 座標を設定
            mTextBox.Size = new Size(this.ClientSize.Width, this.ClientSize.Height - 26);
                                                     // サイズを設定
            mTextBox.Multiline = true;              // 複数行OKにする
            mTextBox.ScrollBars = ScrollBars.Both;  // スクロールバーを表示
            mTextBox.BackColor = Color.White;       // 背景色を白にする
            mTextBox.TabIndex = 2;                  // タブ順は2番目
            mTextBox.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
                                                     // 上下左右とも固定
            Controls.Add(mTextBox);
        }
        
        // ファイル−開くメニューのイベントハンドラ
        private void FileOpen_Clicked(object sender, EventArgs e) {
            OpenFileDialog opendlg = new OpenFileDialog();
            opendlg.Filter = "テキストファイル (*.txt)|*.txt|すべてのファイル (*.*)|*.*" ;
            opendlg.InitialDirectory = mDefaultDirectory;
            if (opendlg.ShowDialog() == DialogResult.OK) {
                // ディレクトリを保存しておく
                FileInfo fi = new FileInfo(opendlg.FileName);
                mDefaultDirectory = fi.DirectoryName;
                
                // ファイルを読み込む
                Stream stm = opendlg.OpenFile();
                if (stm != null) {
                    StreamReader reader = new StreamReader(stm, System.Text.Encoding.GetEncoding("Shift_JIS"));
                    mTextBox.Text = reader.ReadToEnd();
                    stm.Close();
                }
            }
        }
        
        // ファイル−終了メニューのイベントハンドラ
        private void FileExit_Clicked(object sender, EventArgs e) {
            this.Close();
        }
        
        // ヘルプ−バージョン情報メニューのイベントハンドラ
        private void HelpAbout_Clicked(object sender, EventArgs e) {
            MessageBox.Show("Sample3aプログラム Ver 0.1");
        }
        
        // URLを開くボタンのイベントハンドラ
        private void UrlOpen_Clicked(object sender, EventArgs e) {
            new HttpThread(new HttpThread.HttpFinishDelegate(this.Http_Finish));
        }
        
        // HttpThreadでHTTP GETが完了したときに呼ばれるメソッド
        private void Http_Finish(string s) {
            mTextBox.Text = s;
        }
    }
}
Sample6a.cs

コンパイル

csc /t:winexe /r:System.DLL /r:System.Windows.Forms.DLL /r:System.Drawing.DLL Sample6a.cs

これでコンパイルできるはずです。
ファイル名が違うだけで前回と変わってません。

スレッドを作る

別スレッドを作るにはSystem.Threading.Threadクラスをこさえて、Thread.Startメソッドを呼ぶだけです。 Threadクラスをこさえるときにスレッド関数にしたいメソッドを渡します(これも、delegateをこさえて。まぁこういうもんだと思ってください)。 それで、Startメソッドを呼べばコンストラクタで渡したメソッドが別スレッドで動き始めます。
今回は、スレッド用に別クラス(HttpThreadクラス)を作りました。 別クラスにしなくてもいいんですが、こういう風にしたほうがなにかと便利だと思います。

このスレッドを起動するのは134行目のようにHttpThreadクラスをこさえてやるだけです。

別スレッドからFormを呼び出す

ちゃんとドキュメント化されてるのを見つけることはできなかったんですが、別スレッドからFormを更新したりすると、どうもうまく動かないときがあります。 今回みたいにTextBoxに文字列をセットするだけなら動くみたいですが、フォーカスを移動したりといったことをするとちゃんと動きませんでした。 やっぱり、Form.Invokeメソッドを使うのがまっとうな方法のようです。
C++な人ならInvokeはSendMessage APIだと言えばわかりやすいでしょう(PostMessageにあたるBeginInvokeメソッドもあります)。 ようするに、スレッドを切り替えて動いてくれるわけです。

今回は、10行目でコールバック用のdelegateを定義し、134行目のようにHttpThreadクラスのコンストラクタにコールバック用メソッド(のdelegate)を渡してます。 で、HttpThread.Runメソッドの処理が終わったら31行目のようにInvokeメソッドでコールバック用メソッドを呼び出してます。
ここで、delegateってのは実はSystem.Delegateクラスです(ほんとはSystem.MulticastDelegateクラス)。 リファレンスを見るとわかるように、呼び出し先のオブジェクトをTargetプロパティで取り出せます。 こいつをFormにキャストしてInvokeしています。
上のサンプルはdelegateを1つしか登録できないのでこれでいいですが、複数登録できるときは


foreach (Delegate d in mHttpFinishDelegate.GetInvocationList()) {
    Form frm = d.Target as Form;
    if (frm != null) {
        frm.Invoke(d, new object[] { s });
    }
}

こんなふうにしてやればいいと思います。


もうひとつの非同期I/O

上のサンプルでは別スレッドを作ってWebRequestが非同期で動くようにしましたが、実はもともと非同期I/O用のメソッドがWebRequestクラスやStreamクラスなどに用意されています。 WebRequest.BeginGetResponseメソッドやStream.BeginReadメソッドがそれです。
非同期I/O系のメソッドの使い方は共通していて、BeginXXXメソッドにAsyncCallbackデリゲート(につつんだコールバックメソッド)を渡すとI/Oが終わった時点でそのコールバックメソッドが呼ばれるのでそこでごにょごにょするって感じです。 コールバックメソッドは別スレッドから呼ばれるので同期を取るとかGUIをいじるときはForm.Invokeメソッドを使うとかといった必要はあります。

詳しいことはご自分でどうぞ。


[トップ] [目次] [←前]

株式会社ディーバ 青柳 臣一
2002/03/05