Taskの備忘録

  • このエントリーをはてなブックマークに追加

こんにちは。弱小エンジニアのいすいです。

 

最近プロジェクトを引き継いで、コードの改修を行っているのですが、

そのコードが何とも杜撰笑笑

 

元々の担当者はもう辞めちゃった人で、

技術はあるのですが、結構雑な人だったっぽい笑笑

 

スペルミスやら、NGなコーディングしているとか色々あったけど、

特にいまいちと思ったのが、

Taskの付け方!

 

とりあえず色々なところにTaskがつけられて、全てすぐにawaitされている。。

 

まあ自分も実際よく分かってないし、人のこと言えないのですが、、

そんなこんなでこの機会に非同期について学び直しました。

 

Taskで処理する場所

まず、Taskで処理を行うと言うことは、非同期で処理する目的かと思いますが、

根本的に非同期にする場所というのは重い処理の箇所です。

 

何でもかんでも非同期にしてもアプリの性能は良くなりません。

 

具体的に非同期にした方が良いのは、

ファイル読み込みや、APIアクセス、DBアクセスとかですかね。

 

すぐにawaitしない方が良い時

戻り値がTask型だと、awaitで受け待ちにすると思いますが、

すぐにawaitで待たない方が良い時があります。

 

それは非同期にしたい箇所でTask型の変数をまとめてawaitしたい時です。

まとめてawaitするのは以下のようなコードです。

Task taskA = Task.Run(()=>
{
    Thread.Sleep(3000);
});

Task taskB = Task.Run(()=>
{
    Thread.Sleep(3000);
});

Task taskC = Task.Run(()=>
{
    Thread.Sleep(3000);
});

// ここでまとめてawait
await Task.WhenAll(taskA, taskB, taskC);

こうすることで、taskAとtaskB,TaskCを非同期で処理できます。

 

一度ここでawaitについておさらいすると、

awaitが付いている処理では、awaitにたどり着くと、メインスレッドに呼び出し元が戻ります。

つまり、メソッドAからメソッドBを呼び出し、メソッドBのawaitに辿り着いた時、メソッドAのメソッドB呼び出し以降の処理がメインスレッドで続行されます。

それと同時にサブメソッドでメソッドBのawaitの処理が実行され、それが終わると、await以降のメソッドBの処理がメインスレッドに戻ってきて実行されます。

例えば以下の場合、

void MethodA()
{
    MethodB();
    Console.WriteLine("A");
}

async void MethodB()
{
   await MethodC();
   Console.WriteLine("B");
}

async Task MethodC()
{
    await Task.Run(()=>
    {
        Thread.Sleep(3000);
        Console.WriteLine("C");
     });
}

上記例を実行すると、

A
C
B

の順番で出力されます。

MethodCで3秒スリープしている間に、MethodAがメインスレッドで処理を進め、MethodCが完了したあと、MethodBが最後まで実行されます。

 

ちなみに、MethodCの処理中にMethodAの処理は完了してしまうことになります。

 

ここで大事なのは、awaitするとその先は受け待ちになってしまうことです。

その先を非同期で処理したい場合にはawaitを付けてはいけません。

なので、何でもかんでもawaitで呼び出せば良いというわけではないのです。

 

ちなみに、ネットではasync voidはだめっていう記事が多いのですが、

async voidでも良くないですかね?

損はないからasync Taskにしとけってことですかね。。

 

ちなみに、Task.Wait()とかはデッドロックのリスクがあるので、しない方がいいみたいですね。

あまり良く理解していませんが、これは気をつけとこうと思います。

 

Taskを重ねてもあまり意味ない

先ほどのTaskとawaitの話をよく聞いておかないと、

Taskを重ねてしまうような無駄なコードを書いてしまう可能性があります。

例えば以下のようなものです。

async void MethodA()
{
    await Task.Run(async ()=>
    {
         await MethodB();
    });
}

async Task MethodB()
{
    await Task.Run(()=>
    {
         Thread.Sleep(3000);
     });
}

上記のようなコードは実際にありました。。😅😅

awaitしている間は受け待ちになるので、上記のって言ってしまえば以下のと同義です。

async void MethodA()
{
    await Task.Run(()=>
    {
         Thread.Sleep(3000);
     }
}

もちろん、場面によって前者の方が適していることはあるかと思います。

ですが、Taskとawaitの仕組みをしっかり理解していないと、

意味のないコードを書いてしまうので要注意です。

Follow me!

  • このエントリーをはてなブックマークに追加

SNSでもご購読できます。

コメントを残す

*

PAGE TOP