C#5.0 で導入された、awaitとasyncを利用してシンプルな非同期メソッドを作成します。
非同期の呼び出しに対応した非同期メソッドを作成します。
非同期メソッドの書式は以下です。
アクセス識別子 async Task<戻り値の型> メソッド名(引数型1 引数1, 引数型2 引数2, ... 引数型n 引数n)
public async Task<int> MyMethod(int a, int b, string c)
{
// ... メソッドの実装
}
WindowsFormアプリケーションを作成し、下図のフォームを作成します。ボタンとMultilineプロパティをtrueに設定したテキストボックスを配置します。
下記のコードを記述します。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace AsyncMethod
{
public partial class FormMain : Form
{
public FormMain()
{
InitializeComponent();
}
private async void button1_Click(object sender, EventArgs e)
{
int ret = await AsyncAdd(1, 2, 3, 4);
textBox1.Text = ret.ToString();
}
private async Task<int> AsyncAdd(int a, int b, int c, int d)
{
await Task.Delay(3000); //3秒後に終了するTask
return a + b + c + d;
}
}
}
下記のメソッドが非同期メソッドの記述になります。4つのint型の引数を受け取り、戻り値でintを返す非同期メソッドになります。
非同期メソッド内では3秒で終了するタスクを作成し、そのタスクを待つことで処理をブロックします。その後、引数4つの和を戻り値として返します。
private async Task<int> AsyncAdd(int a, int b, int c, int d)
{
await Task.Delay(3000); //3秒後に終了するTask
return a + b + c + d;
}
非同期メソッドの呼び出し側のメソッドでは 'async' キーワードを追加して、非同期メソッドとして定義します。
非同期メソッドの呼び出し時には await キーワードをメソッド名の前に記述します。非同期メソッドの戻り値は通常のメソッドと同じように戻り値で取得できます。
取得した戻り値をテキストボックスに表示します。
private async void button1_Click(object sender, EventArgs e)
{
int ret = await AsyncAdd(1, 2, 3, 4);
textBox1.Text = ret.ToString();
}
button1をクリックすると、button1_Click() メソッドが実行されます。メソッド内の AsyncAdd() メソッドの呼び出しでは、別スレッドで AsyncAdd() メソッドを呼び出します。
awaitが記述されているため、処理はAsyncAdd()メソッドの戻りを待ちますが、ブロックせずに待つ動作になります。AsyncAdd() メソッドの完了まではメッセージループ内を処理し、AsyncAdd() メソッドが完了すると、awaitの次の行から実行を再開します。
AsyncAdd() メソッドでは、Task.Delay(3000);
で3秒間処理を停止しますが、呼び出し元のbutton1_ClickのメインスレッドはブロックされずUIはフリーズしません。
プロジェクトを実行します。下図の画面が表示されます。
[button1]をクリックします。3秒ほど経過するとテキストボックスに "10" が表示されます。AsyncAddメソッドに与えた引数 1,2,3,4 の和になっています。
[button1]をクリックして3秒ほどの間、フォームの操作やテキストボックスへの文字の入力ができ、UIがフリーズしないことも確認できます。AsyncAddメソッドがメインスレッドとは別のスレッドで非同期で実行できていることがわかります。
以下のUIを作成します。
以下のコードを記述します。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace SimpleAsync
{
public partial class FormMain : Form
{
public FormMain()
{
InitializeComponent();
}
private async void button1_Click(object sender, EventArgs e)
{
int result = await ComplexAsyncTask();
textBox1.Text += Convert.ToString(result);
}
private async void button2_Click(object sender, EventArgs e)
{
int result = await ComplexAsyncTask2();
textBox1.Text += Convert.ToString(result);
}
public async Task<int> ComplexAsyncTask()
{
Func<int> asyncJob = () =>
{
//時間のかかる処理
int i = 0;
for (i = 0; i < 1000000000; i++) {
}
return i;
};
int ret1 = await Task.Run(asyncJob);
int ret2 = await Task.Run(asyncJob);
return ret1 + ret2;
}
public async Task<int> ComplexAsyncTask2()
{
Func<int> asyncJob = Proc;
int ret1 = await Task.Run(asyncJob);
int ret2 = await Task.Run(asyncJob);
return ret1 + ret2;
}
public int Proc()
{
//時間のかかる処理
int i = 0;
for (i = 0; i < 1000000000; i++) {
}
return i;
}
}
}
ComplexAsyncTask()メソッドは、タスクの処理関数をラムダ式で記述しています。ComplexAsyncTask2()メソッドはタスクの処理は別メソッドになっており、代入によりデリゲートを作成しています。処理内容は同じです。
ポイントの一つ目は"button1_Click","button2_Click"のメソッドにasync修飾子を記述し、非同期メソッドにします。
(非同期メソッドという名称ですが、実行が非同期になるわけではありません。await でのメソッド呼び出しを待機する制御フローが組み込まれるだけです。)
Clickイベント内の
int result = await ComplexAsyncTask();
を実行することで、非同期のメソッド"ComplexAsyncTask()"を呼び出します。メソッド名の手前に await 演算子があるため、"ComplexAsyncTask()"メソッドの実行が完了するまで、以降の処理は実行されませんが、スレッドがブロックされることもありません。await以降の処理が、"ComplexAsyncTask()"メソッドの終了後に実行されるよう予約されます。
"ComplexAsyncTask()"メソッドの実行が完了すると、メソッドの戻り値がresultに代入されます。本来、"ComplexAsyncTask()"の戻り値はTask<int>ですが、await演算子が指定されている場合は、戻り値(int)の返却となります。
プロジェクトを実行します。下図のフォームが表示されます。
[button1]または[button2]をクリックします。しばらく時間が経過した後計算結果がテキストボックスに表示されます。
非同期で実行されるため、ボタンを押してから計算結果がテキストボックスに表示されるまでの間でもUIはフリーズしません。