非同期処理とは
C#でGUIアプリを作成していると、重い処理を実行しているときにGUIが固まってしまいます。
これは、GUIを更新しているスレッド(以下、UIスレッド)が処理を実行していてGUIの更新まで処理しきれていない状態になっています。
また、固まっているときは「応答なし」と表示されてしまいなんとも言えない動作になってしまいます。。。
そこで非同期処理の出番です。
非同期処理とは、別のスレッドを作成してそちらで重い処理などを行うことでUIスレッドでGUIの更新をしつつ処理を進めていくことができます。
そのためGUIが固まることなくバックグラウンドで処理を進めたり、複数のスレッドで処理をすることで通常よりも速く処理をすることができます。
非同期処理のやり方
ここでは初心者向けということで既存の関数を非同期処理する方法を記載していきたいと思います。
引数、戻り値なしの場合
例えば、以下のようなコードがあったとします。
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
HeavyMethod();
MessageBox.Show("完了しました。");
}
private void HeavyMethod()
{
//重い処理の代わりとして10秒待つ
Thread.Sleep(10000);
}
}
上記の例ではボタン押下時のイベントで重い処理HeavyMethod()を実行しており、
その実行中はGUIの操作は受け付けません。
ここから非同期処理に変換しておこうと思います。
まずはHeavyMethodを非同期で処理するHeavyMethodAsyncを作成します。
private Task HeavyMethodAsync()
{
return Task.Run(() => HeavyMethod());
}
- 非同期メソッドは「~Async」と命名します。これは非同期メソッドであることを明確にするためです。
- Task.Runメソッドを使って別スレッドで実行します。引数はラムダ式で記載しています。
- 戻り値をTaskにします。
次にHeavyMethodAsyncを呼び出すbutton2の押下イベントを作成します。
private async void button2_Click(object sender, EventArgs e)
{
button2.Enabled = false; //クリックイベントを2重で呼び出されないようにするため
await HeavyMethodAsync();
MessageBox.Show("完了しました。");
button2.Enabled = true; //Enableをtrueに戻す
}
- HeavyMethodAsnycを呼び出すときはawaitをつけて呼びだします。awaitを付けることでHeavyMethodAsyncの完了を待つことができます。
この時、UIスレッドは空いた状態になりますのでGUIの操作をすることが可能です。
もしawaitを付けないとHeavyMethodAsyncの完了を待たずにMessageBoxが表示されることになります。(これはこれで並列処理をするときに使います。) - awaitを使用するために関数の宣言でasyncを付けます。
- 非同期処理をすると重い処理をしているときにGUIが操作できてしまうので、2重で処理させないためにbutton2のEnableプロパティを操作して処理の実行中はクリックできないようにします。
引数、戻り値ありの場合
もし、処理に引数、戻り値がある場合は以下のような記述になります。
private int HeavyCalculate(int x, int y)
{
//重い処理の代わりとして10秒待つ
Thread.Sleep(10000);
return x + y;
}
private Task<int> HeavyCalculateAsync(int x, int y)
{
return Task.Run(() => HeavyCalculate(x, y));
}
private async void button3_Click(object sender, EventArgs e)
{
button3.Enabled = false; //クリックイベントを2重で呼び出されないようにするため
int x = 1;
int y = 2;
int ans = await HeavyCalculateAsync(x, y);
MessageBox.Show($"完了しました。\n答えは{ans}です。");
button3.Enabled = true; //Enableをtrueに戻す
}
- HeavyCalculateAsyncの戻り値はTask<TResult>になります。intの場合はTask<int>になります。
- 呼び出す側は通常の関数のように戻り値を取得できます。
注意点
- Task.Runで別スレッドで実行しているときは、GUIの操作を行うことができません。例えば、TextBoxのTextプロパティを変更することはできません。GUIの操作は必ずUIスレッドに戻ってから(await以降)行ってください。
- 非同期で処理中はGUIが操作できるため他のボタン押下して不具合が出ないように気を付ける必要があります。
まとめ
以上のような手順で既存の処理を非同期処理することができました。
今回は初心者向けということで単純に既存の関数を非同期にする方法を記載しましたが、今後は上級者向けの記事も作成していきたいと思います。
非同期処理・並列処理はよく理解せずに使用するとバグの原因になるので初心者の方は注意して使用してください。