C#で非同期処理をしよう

# はじめに
　このベージでは、C#での非同期処理についてサンプルコードを交えて説明します。
　ある程度C#に触れて、「非同期処理をやってみたいな」と思った人向けです。
　「こう書けばこのような処理が出来る」程度にしか書いていないので、説明不足が多いかもしれませんがご了承ください。
　C#の言語バージョンはC# 7.0以上、.NET Framework 4.7程度です。

# 非同期処理とは？
　**非同期処理**と言う言葉について調べても理解が出来ない人は多いと思います。(実際、私もそうでした…)
　そこで、図を交えて説明します。
　
　**非同期処理**には大きく分けて2種類あります。**並列処理**と**バックグラウンド処理**です。**並列処理**はその名の通り、複数のスレッドを用いて処理することを言います。**バックグラウンド処理**は重たい処理や時間がかかる（いつまでかかるかわからない）処理（I/O待ち等）を、別のスレッド（**バックグラウンドスレッド**）を用いて処理することです。
![Parallel.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419430/d5fd18fc-71c4-9991-8389-610301d5de09.png)![Background.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419430/d30e99e4-f6e7-614b-dda7-f75b35cb150f.png)
赤：何らかの処理　緑：UIの更新・変更等　黄色：I/O待ち等

### 非同期処理の利点
　**並列処理**は、同時に処理を行うことが出来るため、例えばゲームしながら宿題をする、といったようなことが出来ます。それと、スレッド資源の有効活用にもなるので、処理によっては同期処理に比べて速くなります。
　**バックグラウンド処理**は、別のスレッドで時間がかかる処理を行うため、UIを更新しているスレッド（UIスレッド）を止めることがない。つまり「UIをフリーズさせることなく」時間がかかる処理を行うことが出来る。

# Threadクラスを用いたタスク並列
~~~cs
using System;
// ThreadクラスはSystem.Threading名前空間に存在
using System.Threading;

namespace Sample {
  class C {
    static void Main()
    {
      Thread thread = new Thread(ThreadWork);
      thread.Start();
      Console.WriteLine("キー入力があるまでスレッドが動き続ける");
      Console.ReadKey();
      // スレッドを止める(スレッドが止まっている可能性がある場合は
      // IsAliveプロパティがtrueか確かめる)
      thread.Abort();
    }
    // スレッドを用いて動かすメソッド
    static void ThreadWork()
    {
      while(true) { 
        Console.WriteLine("ThreadWork内の処理");
        Thread.Sleep(2000);
      }
    }
  }
}
~~~
　このコードは新たにスレッドを生成して、そのスレッドによって処理を行うものです。子の様に異なるタスクを並列に処理する事を**タスク並列**と呼びます。
　しかし、2行も使って並列処理をするのはめんどくさいですよね？　あと、`thread.Abort();`をいちいち使ってスレッドを止めないといけないし・・・。
　それと`Thread`クラスはスレッドを生成するため、生成のイベントを通知したり、新しいスレッドの分だけスタックを確保したり・・・とにかく、内部的な面を見てもめんどくさいです。

# Taskクラスを用いたタスク並列
　そこで、`Task`クラスを使用します！
　`Task`クラスは**スレッドプール**というスレッドの活用法を使用しています。
　スレッドプールは簡単に言うと、スレッドの使い回しです。初期状態では最適に活用できるスレッド数で作成されます。
　スレッドを使い回すことによって、スレッド生成による手間（イベント通知、スタック領域の確保等）を省いています。

サンプルコード

~~~cs
using System;
using System.Threading;
// TaskクラスはSystem.Threading.Tasks名前空間に存在
using System.Threading.Tasks;

namespace Sample {
  class C {
    static void Main()
    {
      Task task = new Task(ThreadWork);
      task.Start();
      Console.WriteLine("キー入力があるまでスレッドが動き続ける");
      Console.ReadKey();
      // Mainメソッドが終了すると自動でTaskが終了する
    }
    // スレッドを用いて動かすメソッド
    static void ThreadWork()
    {
      while(true) { 
        Console.WriteLine("ThreadWork内の処理");
        Thread.Sleep(2000);
      }
    }
  }
}
~~~
これでも十分に動きます。
しかし、普通は「`Task`の生成とともに動かしたい！」というのが殆どなので、TaskクラスにはTaskの生成から処理開始までを一行で行えるメソッドが存在します。それが`Task.Run`メソッドです。

サンプルコード

~~~cs
using System;
using System.Threading;
// TaskクラスはSystem.Threading.Tasks名前空間に存在
using System.Threading.Tasks;

namespace Sample {
  class C {
    static void Main()
    {
      // Task.Run(Action)とTask.Run(Func<Task>)で競合が起こるので、明示的にActionにする
      Task.Run((Action)ThreadWork);
      Console.WriteLine("キー入力があるまでスレッドが動き続ける");
      Console.ReadKey();
    }
    // スレッドを用いて動かすメソッド
    static void ThreadWork()
    {
      while(true) { 
        Console.WriteLine("ThreadWork内の処理");
        Thread.Sleep(2000);
      }
    }
  }
}
~~~
`Task task`と`task.Start();`がいらなくなったので、最初のコードと比べると、かなりコード量は減りました。
`Task.Run`メソッドはかなり便利です。積極的に使いましょう。

# Parallelクラスによるデータ並列
`Parallel`クラスはデータが違うが同じような処理を行う時に非常に便利です。
今回は`Parallel.For`メソッド、`Parallel.ForEach`メソッドについてのみ紹介します。

## Parallel.Forメソッド
　ざっくり説明すると、`for`文の処理を並列に行うメソッドです。

サンプルコード

~~~cs
using System;
// ParallelクラスはSystem.Threading.Tasks名前空間に存在
using System.Threading.Tasks;

namespace Sample {
  class C {
    static void Main()
    {
      Parallel.For(0, 10, id => {
        Console.WriteLine($"並列で動作している部分\t id = {id}");
      });
      Console.WriteLine("並列処理終了!");
    }
  }
}
~~~
#### 実行結果例

~~~
並列で動作している部分   id = 0
並列で動作している部分   id = 2
並列で動作している部分   id = 6
並列で動作している部分   id = 4
並列で動作している部分   id = 8
並列で動作している部分   id = 9
並列で動作している部分   id = 7
並列で動作している部分   id = 1
並列で動作している部分   id = 3
並列で動作している部分   id = 5
並列処理終了!
~~~
　`Parallel.For`メソッドは書き方が`for`文に似ていますよね？（オーバーロードはありますが）`for`で書いた直列処理のコードに一手間加える事によって、異なるデータを並列に処理する事（**データ並列**）が出来ます。
　同時に処理をしているため、`for`文の様に0番目（id = 0）から順番に出力されるとは限りません。

## Parallel.ForEachメソッド
`Parallel.ForEach`メソッドは`foreach`文を並列に処理するメソッドです。

サンプルコード

~~~cs
using System;
// ParallelクラスはSystem.Threading.Tasks名前空間に存在
using System.Threading.Tasks;

namespace Sample {
  class C {
    static void Main()
    {
      int[] arr = { 10, 20, 30, 40, 50, 60, 70, 80, 90 };
      Parallel.ForEach(arr, item => {
        Console.WriteLine($"並列で動作している部分\t item = {item}");
      });
      Console.WriteLine("並列処理終了!");
    }
  }
}
~~~
#### 実行結果例

~~~
並列で動作している部分   item = 30
並列で動作している部分   item = 40
並列で動作している部分   item = 60
並列で動作している部分   item = 10
並列で動作している部分   item = 50
並列で動作している部分   item = 20
並列で動作している部分   item = 80
並列で動作している部分   item = 70
並列で動作している部分   item = 90
並列処理終了!
~~~
　`Parallel.ForEach`メソッドも書き方が`foreach`文に似ていて（オーバーロードはありますが）、`foreach`で書いた直列処理のコードに一手間加える事によって、同じく**データ並列**が出来ます。
　同時に処理をしているため、`Parallel.For`と同様に0番目のコレクションから出力されるとは限りません。

# async/await
---(書きかけ)---随時追加予定

# TPL Dataflow（上級者向け）
---(書きかけ)---随時追加予定
TPLだけ別に書くかも……

# 最後に
　初投稿なので、誤字脱字や不足している部分等がございましたらお知らせしていただけると幸いです。