Top > C# > Taskとasync/await

Taskとasync/await



Task

TaskTask<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は非同期に実行され完了を待たないため、rLongProcess()処理結果を受け取る前に呼び出し元の処理が完了してしまう。
これでは都合が悪いため、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

asyncawaitは メインスレッドをブロックせずにTask(非同期処理)の完了を待つための仕組みともいえる。
asyncがそのメソッドやラムダが非同期処理であることを指定するキーワード。
awaitがTask(非同期処理)が完了するのを待つ演算子。
詳しくはこちら
ひとつ上の例をasyncawaitを使って書き直すと以下のようになる。


[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());
}