第143回 Task、async、await について
公開日:2018-06-26 更新日:2019-05-11
1. 概要
Taskを使用して非同期処理を行う方法について説明しています。
2. 動画
3. サンプル1
1. ソース
public class TaskTest1
{
public void Test()
{
var task = TestTask();
task.Wait();
}
private Task TestTask() {
var task = Task.Run(() => {
Thread.Sleep(10000);
});
return task;
}
}
2. 説明
Task.Run() の引数として渡されたラムダ式が別スレッドで動きます。
task.Wait() で、別スレッドが終了するのを待機しています。
task.Wait() で、別スレッドが終了するのを待機しています。
3. サンプル2
1. ソース
public class TaskTest2
{
public void Test()
{
var task = TestTask();
Console.WriteLine("1");
}
private async Task TestTask() {
var task = Task.Run(() => {
Thread.Sleep(10000);
Console.WriteLine("2");
});
await task;
Console.WriteLine("3");
}
}
2. 説明
await task; で、別スレッドで動いているタスクの処理が終わっていない場合は、一旦呼び元へ return します。
この時、await で指定したタスクが、メソッドの戻り値として返ります。
その後、別スレッドの処理が終わると、メインスレッドが await の次の処理から復帰して、それ以降の処理を行います。
この時、await で指定したタスクが、メソッドの戻り値として返ります。
その後、別スレッドの処理が終わると、メインスレッドが await の次の処理から復帰して、それ以降の処理を行います。
3. サンプル3
1. ソース
public class TaskTest2_2
{
public void Test()
{
TestTask();
}
private async void TestTask() {
var task = RunTask();
string result = await task;
//string result = task.Result; //結果が出るまで待機。
//呼び元には戻らない。
}
private Task<string> RunTask() {
var task = Task.Run(() => {
Thread.Sleep(15000);
return "Hello";
});
return task;
}
}
2. 説明
RunTask() の戻り値が Task となっています。
これは、このメソッド本体の戻り値が Task で、
別スレッドの戻り値が string であることを示しています。
また、Task のタスクを await すると、Task が保持している string が返ります。
await は、
別スレッドのタスクが終了していない場合は、現在実行中のメソッドの呼び元に処理を戻し、
別スレッドのタスクが終了したら、Task の Result の値を返します。
例えば、
string result = await task; の箇所は、別スレッドのタスクが終了すると、
string result = "Hello" の形になります。
これは、このメソッド本体の戻り値が Task
別スレッドの戻り値が string であることを示しています。
また、Task
await は、
別スレッドのタスクが終了していない場合は、現在実行中のメソッドの呼び元に処理を戻し、
別スレッドのタスクが終了したら、Task の Result の値を返します。
例えば、
string result = await task; の箇所は、別スレッドのタスクが終了すると、
string result = "Hello" の形になります。
3. サンプル4
1. ソース
public class TaskTest2_3
{
public void Test()
{
var task = TestTask();
string s = task.Result; //デッドロック
}
private async Task<string> TestTask() {
var task = RunTask();
string result = await task;
//string result = await task.ConfigureAwait(false);
//_lblResult.Text = "OK"; //エラー
_form.Invoke((MethodInvoker)(() => {
_lblResult.Text = "OK";
}));
return result; //ここで task.Result に値が入る
}
private Task<string> RunTask() {
var task = Task.Run(() => {
Thread.Sleep(10000);
return "Hello";
});
return task;
}
}
2. 説明
上記はデッドロックを起こすサンプルです。
メインスレッド(UIスレッド)で task.Result を取得していますが、ここで Result(別スレッドの戻り値) に値が入るまで待機します。 待機と言う処理をしているため、メインスレッドで他の処理を行うことはできません。 await と異なり、呼び元にも戻りません。
タスクの Result は、await task から復帰した後の return で設定されます。
ここはメインスレッドで動きますが、メインスレッドは最初に Result で待機中となっているため、await から復帰することができないため、Result が設定できなくなります。
これにより、自分自身でデッドロックの状態になります。
デッドロックは、await task.ConfigureAwait(false); とすることで回避できます。
これは、await の復帰後の処理を、別スレッドに行わせます。
但し、UIスレッドではなくなるため、フォームのコントロールは通常の方法では参照できなくなるため、注意が必要です。
メインスレッド(UIスレッド)で task.Result を取得していますが、ここで Result(別スレッドの戻り値) に値が入るまで待機します。 待機と言う処理をしているため、メインスレッドで他の処理を行うことはできません。 await と異なり、呼び元にも戻りません。
タスクの Result は、await task から復帰した後の return で設定されます。
ここはメインスレッドで動きますが、メインスレッドは最初に Result で待機中となっているため、await から復帰することができないため、Result が設定できなくなります。
これにより、自分自身でデッドロックの状態になります。
デッドロックは、await task.ConfigureAwait(false); とすることで回避できます。
これは、await の復帰後の処理を、別スレッドに行わせます。
但し、UIスレッドではなくなるため、フォームのコントロールは通常の方法では参照できなくなるため、注意が必要です。