「C Sharpの基礎 - マルチスレッド」の版間の差分
103行目: | 103行目: | ||
== ConfigureAwait(false) == | == ConfigureAwait(false) == | ||
GUIアプリケーションでは、以下のように記述すると、簡単にデッドロックが発生する。<br> | |||
なぜなら、TaskのWaitメソッドやResultは、元のスレッドへ戻ろうとするからである。<br> | |||
<br> | |||
以下の場合、methodAsync().Wait()を呼んだ時点で親スレッドがスリープする。<br> | |||
なぜなら、methodAsync()の終了後に親スレッドに戻ろうとするが、既に親スレッドがスリープしているため、<br> | |||
処理を続けることができないからである。<br> | |||
その結果、親スレッドは子スレッドを待ち続け、子スレッドは親スレッドに戻ろうとして、デッドロックが発生する。<br> | |||
<source lang="c#"> | <source lang="c#"> | ||
// デッドロックが起きるサンプルコード(GUIアプリケーションのみ) | // デッドロックが起きるサンプルコード(GUIアプリケーションのみ) | ||
// コンソールアプリケーションでは正常に動作する | |||
public void callMethod() | public void callMethod() | ||
{ | { | ||
117行目: | 123行目: | ||
} | } | ||
</source> | </source> | ||
<br> | <br> | ||
対策として、以下のように記述するとデッドロックが回避できる。(戻るスレッドはどこでもよい場合)<br> | 対策として、以下のように記述するとデッドロックが回避できる。(戻るスレッドはどこでもよい場合)<br> | ||
<source lang="c#"> | <source lang="c#"> | ||
// デッドロックが起きないサンプルコード | // デッドロックが起きないサンプルコード(GUIアプリケーションのみ) | ||
public void callMethod() | public void callMethod() | ||
{ | { | ||
130行目: | 132行目: | ||
} | } | ||
public async Task | public async Task methodAsync() | ||
{ | { | ||
return await Task.Delay(1000).ConfigureAwait(false); | return await Task.Delay(1000).ConfigureAwait(false); |
2020年3月8日 (日) 04:35時点における版
概要
.NET Framework 4.5で導入されたC# 5.0(Visual Studio 2012) からは、マルチスレッドプログラミングをコンパイラレベルでサポートしている。
具体的には、asyncとawaitというキーワードが導入された。
これは、タスクベース非同期パターン(Task-based Asynchronous Pattern、TAP)を実装するものである。
このタスクベース非同期パターンでは、処理の開始はMethodNameAsyncという形式をとる。
そしてその戻り値を、Task、Task<T>、voidとし、Taskを待つにはawaitを付加する。
また、awaitを付加した関数には、asyncを付加する。
下記の例は、Task<int>型を返す非同期関数である。
public async Task<int> MethodAsync()
{
var iRet = await Task.Run(() =>
{
await Task.Delay(5000);
return 0;
});
return iRet;
}
asyncとawaitの動作
Taskは、一連の処理をひとまとまりにした単位である。
Taskを呼び出した際はマルチスレッドで実行されるようになっており、連続で記述する場合は、記述した個数だけマルチスレッドで並列処理される。
public async Task MethodAsync()
{
var task1 = Task.Run(() =>
{
// 何か処理1
});
var task2 = Task.Run(() =>
{
// 何か処理2
});
var task12 = Task.WhenAll(task1, task2);
var taskAll = task12.ContinueWith(() =>
{
// 何か処理3
});
return await taskAll.ConfigureAwait(false);
}
Task.WhenAllとContinueWith
TaskはContinueWith関数を利用して、その後の処理を連続して実行できる。
2つ以上のスレッドを実行して全スレッドの処理が終了した後、続けてスレッドの処理を書く場合は、
ContinueWithまたはawaitを使用してスレッドの終了を待つ。
// パターン1
var task1 = Task.Run(() =>
{
// 何か処理
});
var task2 = Task.Run(() =>
{
// 何か処理
});
var task3 = Task.Run(() =>
{
// 何か処理
});
var taskAll = Task.WhenAll(task1, task2).ContinueWith(task3); // awaitは不要
// パターン2
var task1 = Task.Run(() =>
{
// 何か処理
});
await task1.ConfigureAwait(false);
var task2 = Task.Run(() =>
{
// 何か処理
});
await task2.ConfigureAwait(false);
var task3 = Task.Run(() =>
{
// 何か処理
});
await task3.ConfigureAwait(false);
状況にもよるが、処理が膨大になると可読性が落ちるので、
先に関数で纏めて処理を記述して関数を呼ぶだけでタスク間の関係性が見通せるパターン1の方が良い。
ConfigureAwait(false)
GUIアプリケーションでは、以下のように記述すると、簡単にデッドロックが発生する。
なぜなら、TaskのWaitメソッドやResultは、元のスレッドへ戻ろうとするからである。
以下の場合、methodAsync().Wait()を呼んだ時点で親スレッドがスリープする。
なぜなら、methodAsync()の終了後に親スレッドに戻ろうとするが、既に親スレッドがスリープしているため、
処理を続けることができないからである。
その結果、親スレッドは子スレッドを待ち続け、子スレッドは親スレッドに戻ろうとして、デッドロックが発生する。
// デッドロックが起きるサンプルコード(GUIアプリケーションのみ)
// コンソールアプリケーションでは正常に動作する
public void callMethod()
{
methodAsync().Wait();
}
public async Task methodAsync()
{
return await Task.Delay(1000);
}
対策として、以下のように記述するとデッドロックが回避できる。(戻るスレッドはどこでもよい場合)
// デッドロックが起きないサンプルコード(GUIアプリケーションのみ)
public void callMethod()
{
methodAsync().Wait();
}
public async Task methodAsync()
{
return await Task.Delay(1000).ConfigureAwait(false);
}