2016年9月13日火曜日

[C#]async/awaitの挙動について

async/awaitの挙動について


以下のサンプルコードにて動作確認を行ってみた。

実行結果は、

---start---
main1=9
test1=9
test2=12
test3=11
test4=12
test5=12
test6=12
test8=11
test7=11
---end---
test9=11
---check1 start---
main2=9
test1=9
test2=11
test3=12
test4=12
test5=11
test6=12
test8=12
---check1 end---
test9=12
test7=11



〇テスト1:Taskの生成した前後でasync/awaitした場合の挙動を調べる
・Taskを生成されるまで同期処理(test1)
・Taskを生成するとスレッドIDが変わる(test2)
・Taskを生成した内側でasync/awaitするとスレッドIDが変わる(test3, test5)
・Taskを生成した外側でasync/awaitすると同期処理(test4)
・純粋にTaskを生成してStartした場合も同じ結果になる(test6)
・純粋にTaskを生成した場合、async/awaitした数分だけ実行サイクルが遅延する(test7, test8, test9)
・純粋にTaskを生成した内側でasync/awaitすると処理順序を守らない(test7, test9)
→完全な非同期処理を行うならば、Taskの付いた命令は必ず最上位でTask.Runする必要がある。

〇テスト2:awaitではなくwait()した場合の挙動を調べ宇r
・Taskを生成する前でasync/awaitするとフリーズする(test1, test5, test6, test8)
・純粋にTaskを生成した場合、中のactionはasync/awaitする必要がある(test6, test8)
→純粋にTaskを生成した場合、Task内Actionをasync/awaitする必要がある。


まとめると
・必ず最上位だけにTask.Runを使う(その関数名の後ろにTaskやAsyncなど名前を付けて置くと分かりやすい)
・async/awaitはTask.Runされた中でしか使ってはいけない(外で使うとフリーズする)


必ずしも上記結論が100%正しいというわけではないですが、
Taskを使う運用ルールを決めてあげないとあっちでは動いてこっちでは動かないという
モグラ叩き現象が発生する恐れがあるので、一番動きやすく覚えやすい運用ルールを決めた方が良いです。




---以下テストコード---

private async void Form1_Load(object sender, EventArgs e)
{
    Console.WriteLine("---start---");
    Console.WriteLine($"main1={Environment.CurrentManagedThreadId}");
    await test1();
    await test2();
    await test3();
    await test4();
    await test5();
    await test6();
    await test7();
    await test8();
    await test9();
    Console.WriteLine("---end---");
    await Task.Delay(100);
    Console.WriteLine("---check1 start---");
    await check1();

    Console.WriteLine("---check2 start---");
    //test1().Wait();   //freeze
    test2().Wait();
    test3().Wait();
    test4().Wait();
    //test5().Wait();   //freeze
    //test6().Wait();   //freeze
    test7().Wait();
    //test8().Wait();   //freeze
    test9().Wait();
}

async Task check1()
{
    Console.WriteLine($"main2={Environment.CurrentManagedThreadId}");
    await test1();
    await test2();
    await test3();
    await test4();
    await test5();
    await test6();
    await test7();
    await test8();
    await test9();
    Console.WriteLine("---check1 end---");
}


async Task test1()
{
    await Task.Delay(1);
    Console.WriteLine($"test1={Environment.CurrentManagedThreadId}");
}

Task test2() => Task.Run(() =>
{
    Console.WriteLine($"test2={Environment.CurrentManagedThreadId}");
});

Task test3() => Task.Run(async () =>
{
    await Task.Delay(1);
    Console.WriteLine($"test3={Environment.CurrentManagedThreadId}");
});

async Task test4() => await Task.Run(() =>
{
    Console.WriteLine($"test4={Environment.CurrentManagedThreadId}");
});

async Task test5() => await Task.Run(async () =>
{
    await Task.Delay(1);
    Console.WriteLine($"test5={Environment.CurrentManagedThreadId}");
});

Task test6()
{
    var result = new Task(() =>
    {
        Console.WriteLine($"test6={Environment.CurrentManagedThreadId}");
    });
    result.Start();
    return result;
}

Task test7()
{
    var result = new Task(async () =>
    {
        await Task.Delay(1);
        Console.WriteLine($"test7={Environment.CurrentManagedThreadId}");
    });
    result.Start();
    return result;
}

async Task test8()
{
    var result = new Task(() =>
    {
        Console.WriteLine($"test8={Environment.CurrentManagedThreadId}");
    });
    result.Start();
    await result;
}

async Task test9()
{
    var result = new Task(async () =>
    {
        await Task.Delay(1);
        Console.WriteLine($"test9={Environment.CurrentManagedThreadId}");
    });
    result.Start();
    await result;
}

コメントを投稿

Androider