2009年11月25日

C# Invoke/BeginInvoke でMulti-Thread −UIのフリーズを解消する

こんなことがありませんか:プログラムに重い作業を連続的にさせるとユーザインタフェースがフリーズのようになって、何秒ないし何分後にやっと応答が戻る。
これを解消するためにやはりMulti-Thread。
忘れないために今のうちに例のプログラムを作りました。内容はつまらなくて簡単:1〜100,000までの数字のなか、13で割れる数字をピックアップし、TextBoxに足していく。さらに数字の個数をリアルタイムにLabelで表示する。
まずSingle-ThreadとMultiThreadの実行画面の比較です:

Single-ThreadMulti-Thread-Invoke 

●上の方:Single-Threadの画面です
リアルタイムに個数を表示できず、Windowを動かそうとしたら応答してくれなくなる。計算がすべて終了後に、個数が表示されて応答もするようになる。
●下の方:Multi-Threadの画面です。
個数がリアルタイムに表示してくれて、Windowを動かしてもちゃんと反応してくれる。
ソースは下記の通りです。

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

namespace Multi_Thread
{
public partial class Form1 : Form
{

int count;
private delegate void UpdateUIHandler(int i);

public Form1()
{
InitializeComponent();
this.count = 0;
}

private void button1_Click(object sender, EventArgs e)
{
this.count = 0;
this.textBox1.Clear();
this.label1.Text = string.Empty;

//A: すべての作業(計算及びUI更新は UI Threadから実行
// DoAllWorkFromMainThread(); //完成時間:10秒

//B:別のThreadを立ち上げて、計算を行う。さらに
Thread anotherThread = new Thread(new ThreadStart(DoWorkFromSeparateThread));
anotherThread.Start();
}

private void DoAllWorkFromMainThread()
{

for (int i = 1; i <= 100000; ++i)
{
if (i % 13 == 0)
{
this.textBox1.AppendText(i.ToString() + " ");
++count;
this.label1.Text = this.count.ToString();
}
}
}

private void DoWorkFromSeparateThread()
{
for (int i = 1; i <= 100000; ++i)
{
if (i %13 == 0)
{
++count;
object[] paraList = { i };


//1, BeginInvokeを使う:anotherThreadはUI Threadを待たず、ひたすら計算結果をUI Threadに押し付ける
//this.BeginInvoke(new UpdateUIHandler(UpdateUI), paraList); //10秒
//2 Invokeを使う:anotherThreadはUI Threadの作業完了まで待って、次の計算を行う。
this.Invoke(new UpdateUIHandler(UpdateUI), paraList); //14秒

}
}
}

private void UpdateUI(int i)
{
this.textBox1.AppendText(i.ToString() + " ");
this.label1.Text = this.count.ToString();
return;
}
}
}



完全なソースがここです。






Multi-Threadを使う要領は、要するに:


1、計算を行うMethod(たとえばA)を作る


2、UIを更新するMethod(たとえばB)作る


3、Aの中で、main threadのControlのInvoke或いはBeginInvokeでBを呼び出す。


4、Main threadから新しいThreadを立ち上げて、Aを実装する。






ここで、InvokeとBeginInvokeはsynchronousとasynchronousの違いです。


両者とも、Main Thread(UI Thread)から実行するのがルールです。






Invoke:Main Threadの作業が終わるまでまって、Work Threadの次の作業に入る


BeginInvoke:Main Threadの作業を待たずにWork Threadがどんどん作業して結果を寄せる。






上司と部下の関係で形容すれば分かりやすいかな。(不適切かもしれないけど説明のため)


上司と部下が協同してある検品作業を行う。実際に検品するのが部下(Work Thread)で、上司(UI Thread)の役割は1個ずつ検品の結果を黒板に書く。


Invokeの場合は、上司から部下に「俺が書き終わるまでに次の結果渡すな!」といって、余裕を作って、ほかの部門からの依頼もヒヤリングしている。


BeginInvokeは、上司が書き終わったかどうか関係なく、部下がどんどん結果を寄せてくる。それで上司が手が回らなくて、余裕がなくなり、ほかの仕事に応答できなくなる。


ここで一つの可能性は、もし、検品という作業はすごく複雑で時間がかかり、必然的に部下が追いつかない場合は、上司はわざわざ「待て!」と声明しなくてもOK。この場合はBeginInvokeを使ってもいいでしょう。


基本的に、重い仕事を部下にさせて、上司が余裕を待つ。ただ、逆の場合もあるため、Invokeでコントロールする必要が出てきます。


ちなみに、スペックの低いCPU(シングルコア)ではあまりMulti-Threadのメリットを感じない場合があります。

posted by 正在想 at 00:36| Comment(0) | C# | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント: