レガシーコードからの脱却

レガシーな職場でも独学でどうにか頑張っていくブログです。

【お勉強記録】C#文法学び直し。並列処理2

前回に引き続き並列処理について

Task

Taskクラスは.NET Framework 4から使えるクラスで、重い処理を最小限に抑えるためにうまくやってくれているそうです。

前回のを書き換えてみました。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ThreadTest
{
    class Program
    {
        static void Main(string[] args)
        {
            object obj = new object();

            Task task = Task.Factory.StartNew(() => { new Counter("タスク", 5).Play(); });
            new Counter("メイン", 3).Play();

            Console.ReadLine();
        }
    }
}

実行するとうまく並行処理できています。

StartNewメソッドは引数としてActionデリゲートが指定されています。 上の例ではTaskのインスタンスを作って実行していますが、Task.Runを使用すると、タスクの作成から実行までやってくれます。

static void Main(string[] args)
{
    Task.Run(
        () => { new Counter("タスク", 3).Play(); }            
        );
    new Counter("メイン", 3).Play();
    Console.ReadLine();
}

同期についてですが、TaskクラスのWaitを用いることでそのタスクの終了を待つことができるようです。

Task task = Task.Factory.StartNew(() => { new Counter("タスク", 5).Play(); });
new Counter("メイン", 1).Play();
task.Wait();
new Counter("同期後処理", 3).Play();
Console.ReadLine();

実行するときちんとタスクの処理が終了してから同期後処理と出力されるのが確認できます。

Waitメソッドによってそのタスクが終了するのを待ってから処理を行うことができるのは上の通りです。

複数タスクを作ったときはすべてWaitを実装しなくてはならないのかと思いきや、Taskクラスでは、複数タスクを同期するため「WaitAll」メソッドが用意されているとのこと。

        static void Main(string[] args)
        {
            
            Task taskA = Task.Factory.StartNew(() => { new Counter("タスクA", 3).Play(); });
            Task taskB = Task.Factory.StartNew(() => { new Counter("タスクB", 3).Play(); });
            Task taskC = Task.Factory.StartNew(() => { new Counter("タスクC", 3).Play(); });
            Task taskD = Task.Factory.StartNew(() => { new Counter("タスクD", 3).Play(); });
            //全てのタスクの終了を待つ
            Task.WaitAll(taskA, taskB, taskC, taskD);
            new Counter("同期後処理", 2).Play();  
          
            Console.ReadLine();
        }

f:id:Gappory:20170812173643p:plain

ちなみにTask.WaitAnyメソッドも用意されていて、これは文字通り引数に渡されたタスクが一つでも終了した時点をとらえられるようです。

Parallelクラス

Parallelクラスを使うことで、複数タスクをよりシンプルに書くことができます。

Parallel.Invoke(
    () => { new Counter("タスクA", 3).Play(); },
    () => { new Counter("タスクB", 3).Play(); },
    () => { new Counter("タスクC", 3).Play(); },
    () => { new Counter("タスクD", 3).Play(); }                
    );                                             
Console.ReadLine();

ParallelクラスにはForやForEachメソッドも用意されています。

async と await

C# 5.0から非同期メソッドが導入され、これによってさらにシンプルに並行処理を実現できるようになったそうです。

非同期メソッドにはasync修飾子をつけ、処理内でTaskにawaitをつけることで、Taskの完了を待つことができます。 非同期メソッド内でタスクの順番を指定できるというイメージでしょうか? 難しい…

とりあえず、awaitの有無の差を書いて試してみました。

        static void Main(string[] args)
        {
            Task task = Program.PlayAsync();            
            Console.ReadLine();
        }

        static async Task PlayAsync()
        {
            for (int i = 0; i < 10; i++)
            {
                int x = i;
                //awaitを付ける。
                await Task.Run(() => new Counter("タスク" + x.ToString(), 2).Play()); 
            }
        }

f:id:Gappory:20170812191723p:plain

        static void Main(string[] args)
        {
            Task task = Program.PlayAsync();            
            Console.ReadLine();
        }

        static async Task PlayAsync()
        {            
            for (int i = 0; i < 10; i++)
            {
                int x = i;
                //awaitを付けない。
                Task.Run(() => new Counter("タスク" + x.ToString(), 2).Play());
            }            
        }        

f:id:Gappory:20170812191849p:plain

なんとなくですが、違いが見えま…す?

非同期メソッドの戻り値はTaskかTask型のみみたいです。 そのため、非同期メソッド自体をTaskとして扱えます。

        static void Main(string[] args)
        {            
            Task task = Program.PlayAsync();
            new Counter("メイン", 10).Play();
            Console.ReadLine();
        }

        static async Task PlayAsync()
        {            
            for (int i = 0; i < 10; i++)
            {
                int x = i;
                await Task.Run(() => new Counter("タスク" + x.ToString(), 2).Play());
            }            
        }        

むっずーーー!!! async/awaitに関しては正直100%の理解はまだ私には難しいですね… 非同期メソッドの書き方はまぁまぁわかるのですが、きっと利用のされ方ですね鍵は…??

書籍や下記のサイトを使って勉強してみましたが、非同期メソッド結構難しいです。

まだ感覚的理解の枠を抜け出せていない気がします。

ソース等で見かけた際はじっくり読むようにしてしっかり理解したいです。

参考サイト

非同期メソッド - C# によるプログラミング入門 | ++C++; // 未確認飛行 C

Taskを極めろ!async/await完全攻略 - Qiita