Taskとasync/await
Task
Task、
Task<TResult>は
Action、Funcデリゲートを非同期に操作するもの。
ここでいう非同期とは、呼び出し元のスレッドとは別のスレッドで処理が実行されることを指す。
LongProcess()
メソッドをTaskで実行する場合、以下のようになる。
public class Task_async_await(ITestOutputHelper output)
{
private string LongProcess(int cnt)
{
var sb = new StringBuilder();
for (int i = 1; i <= cnt; i++)
{
System.Threading.Thread.Sleep(100);
sb.Append(i.ToString());
}
return sb.ToString();
}
[Fact]
public void a()
{
string r = string.Empty;
var t = Task.Run(() => {
r = LongProcess(5);
});
output.WriteLine(r);
Assert.Equal(string.Empty, r);
}
}
Task.Run()
でTaskの作成と開始を同時に実施している。(Taskのインスタンスを作る方法もあるがここでは割愛)
Taskは非同期に実行され完了を待たないため、r
にLongProcess()
の処理結果を受け取る前に呼び出し元の処理が完了してしまう。
これでは都合が悪いため、Taskが完了するのをWait()
やResult
で待つことができる。
(※呼び出しっぱなしで結果をまったく気にしない処理であればこれはこれで別にいい)
[Fact]
public void b()
{
string r = string.Empty;
var t = Task.Run(() => {
r = LongProcess(5);
});
t.Wait();
output.WriteLine(r);
Assert.Equal("12345", r);
}
[Fact]
public void c()
{
var t = Task.Run(() => {
return LongProcess(5);
});
string r = t.Result;
output.WriteLine(r);
Assert.Equal("12345", r);
}
Wait()
やResult
でTaskが完了するのを待つことができるが、完了するまで現在のスレッドがブロックされるためGUIアプリケーションなどでデッドロックが発生する可能性がある。
また、Wait()
やResult
は同期的に結果を取得しているので、そもそもとしてTask(非同期処理)を使っている意味がない。
asyncとawait
asyncと
awaitは
メインスレッドをブロックせずにTask(非同期処理)の完了を待つための仕組みともいえる。
async
がそのメソッドやラムダが非同期処理であることを指定するキーワード。
await
がTask(非同期処理)が完了するのを待つ演算子。
詳しくはこちら。
ひとつ上の例をasync
とawait
を使って書き直すと以下のようになる。
[Fact]
public async Task b2()
{
string r = string.Empty;
var t = Task.Run(() => {
r = LongProcess(5);
});
await t;
output.WriteLine(r);
Assert.Equal("12345", r);
}
[Fact]
public async Task c2()
{
var t = Task.Run(() => {
return LongProcess(5);
});
string r = await t;
output.WriteLine(r);
Assert.Equal("12345", r);
}
Taskの呼び出し前にawait
キーワードを指定することで、呼び出し元スレッドをブロックすることなくTask(非同期処理)の完了を待つことができる。
(呼び出し元のスレッドはTaskの完了を待つ間に別の処理をすることができる。)
また、複数のTaskをawait
で処理したい場合は、以下のように順番に実行すればよいが...
[Fact]
public async Task d()
{
var sb = new StringBuilder();
await Task.Run(() => sb.Append(LongProcess(5)));
await Task.Run(() => sb.Append(LongProcess(4)));
await Task.Run(() => sb.Append(LongProcess(3)));
output.WriteLine(sb.ToString());
Assert.Equal("123451234123", sb.ToString());
}
...実行順序を気にしない場合は、Task.WhenAll
を使えば並列に実行でき、全体的な処理時間を短縮することができる。
[Fact]
public async Task d2()
{
var sb = new StringBuilder();
var tList = new List<Task>();
tList.Add(Task.Run(() => sb.Append(LongProcess(5))));
tList.Add(Task.Run(() => sb.Append(LongProcess(4))));
tList.Add(Task.Run(() => sb.Append(LongProcess(3))));
await Task.WhenAll(tList);
output.WriteLine(sb.ToString());
Assert.Equal("123123412345", sb.ToString());
}