タスクを使ってUIをフリーズさせずに負荷の高い処理をする
なんの工夫もなく負荷の高い処理を記述すると、その間はユーザーインターフェースがフリーズします。
せっかくウインドウ形式のプログラムを書いているのに、ウインドウが動かせないとか最悪です。
こういった場合は、スレッドを使ってユーザーインターフェースから切り離して負荷の高い処理を実行します。とはいえ、開発チーム要員のスキルレベルが一定ではない場合、スレッドを使ったコードを書くとバグの温床になってしまいます。
ここでマイクロソフトさんが提案してくれたC#言語の機能が、タスク Task です。実際のところはスレッドなのですが、スレッドのコードを書いているという感覚が希薄なプログラミングが可能です。
とはいえ、やはりスレッドなので、ユーザーインターフェースのリフレッシュなどのコードを書くには、ひと手間が必要です。
下記のようなプログラムを作った場合のコードを示します。Form1 に button1, button2, button3 を配置して試してみてください。
button1 が、何も考えずに適当に書いたコードです。
button2 が、タスクを開始して、その終了待ちをしてからメッセージボックスを表示するコードです。
button3 が、タスクを開始した直後にメッセージボックスを表示して、タスクの終了待ちは気にしないコードです。
button2_Click のメソッド名の前の部分に注意してください async という予約語が付加されています。
内部で await が用いられている場合は、async をつけなくてはなりません。
using System;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace my_app
{
public partial class Form1 : Form
{
const int LOOP = 255;
const int MSEC_WAIT = 10;
int Counter = 0;
public Form1()
{
InitializeComponent();
button1.Text = "normal.";
button2.Text = "await Task.Run()";
button3.Text = "Task.Run()";
}
private void button1_Click( object sender, EventArgs e )
{
// UIがフリーズする.
Counter = 0;
for ( int n = 0; n < LOOP; n++ )
{
System.Threading.Thread.Sleep( MSEC_WAIT );
Counter++;
this.Invalidate( true );
}
// ループが終わってから表示される.
MessageBox.Show( "After LOOP." );
}
// async が付加されていることに注目.
private async void button2_Click( object sender, EventArgs e )
{
// UIがフリーズしない.
// await で Task の終了待ちをするのでメッセージボックスはループ完了後に表示される.
// button2_Click の前に async がついていることに注目.
// await をつけるとタスクの終了を待つ.
await Task.Run(() => {
Counter = 0;
for ( int n = 0; n < LOOP; n++ )
{
System.Threading.Thread.Sleep( MSEC_WAIT );
Counter++;
this.Invoke((Action)(() =>
{
this.Invalidate();
}));
}
});
// タスクが終わってから表示される.
MessageBox.Show( "After Task.Run();" );
}
private void button3_Click( object sender, EventArgs e )
{
// UIがフリーズしない.
// await がないのでタスクの終了待ちをしない.
Task.Run(() => {
Counter = 0;
for ( int n = 0; n < LOOP; n++ )
{
System.Threading.Thread.Sleep( MSEC_WAIT );
Counter++;
this.Invoke((Action)(() =>
{
this.Invalidate();
}));
}
});
// タスクの開始直後に表示される.
MessageBox.Show( "After Task.Run();" );
}
private void Form1_Paint( object sender, PaintEventArgs e )
{
// タイトルバーにカウンタ値を表示する.
this.Text = Counter.ToString();
// ウインドウ色を変更する.
byte level = (byte)( Counter );
this.BackColor = System.Drawing.Color.FromArgb( 0x00, level, level );
}
}
}