【C#】Task初心者のTask初心者によるTask初心者の為のTask入門

#背景
`Task`ってなんだか小難しい割に初心者向きの、というか`Task入門`的な記事が少ない気がする。というか少ないです。

以前書いた、[【C#】Cドライブ以下にある全てのファイルパスを非同期かつIEnumerableに取得してみた](https://qiita.com/OXamarin/items/51c0d713910895a9ccba)という記事ではかなり丁寧に書いたつもりだったが**「コピペで動くようなものを書け」**という要望があったのでこの記事ではコピペで動く、そして分からないが分かるをコンセプトに書いてみました。

#問題提起
同期の後輩がこのような事で悩んでいる、という事を聞きました。
> `WinForm`で`Timerイベント`を使わずに1秒毎にファイルの中身を取得したい。
> `Timerイベント`を使いたくない理由としては何故かウィルスソフトに過剰検知されてしまうからで、`Task`を使って実現してみたい。だが、イマイチ書き方がわからない…。

おｋ把握。つまり、こういう事ですね。

```C#:Form1.cs
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApp6
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        /// <summary>
        /// ロードイベント
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Form1_Load(object sender, EventArgs e)
        {
            Task.Run(async () =>
            {
                var builder = new StringBuilder();
                var csvFileName = @"C:\temp\hoge.csv";

                while (true)
                {
                    byte[] result;
                    builder.Clear();

                    using (var file = new FileStream(csvFileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
                    {
                        result = new byte[file.Length];

                        await file.ReadAsync(result, 0, (int)file.Length);
                    }
                    
                    foreach (char c in result)
                    {
                        if (char.IsLetterOrDigit(c) || char.IsWhiteSpace(c))
                        {
                            builder.Append(c);
                        }
                    }

                    Invoke(new Action(() =>
                    {
                        textBox1.Text = builder.ToString();
                    }));

                    await Task.Delay(1000);
                }
            });
        }
    }
}
```
実行画面
![非同期7.gif](https://qiita-image-store.s3.amazonaws.com/0/172223/5be02ec4-d3f7-90a9-937a-4846dc4069ce.gif)

コメント無しでも分かる方はきちんと理解できていると思います。
解説は最後にして、一旦は根本的な所から見直していきましょう。

# なぜTaskを使う必要があるのか
さて、そもそも論、何故`Task`が必要なんでしょうか。

この記事を読んでいるという事はふんわりだとしてもなんとなく理解しているはずで、
「`Task`を使えば画面が固まらなくなる」という認識で大丈夫だと私は思っています。
上記のプログラムも、画面を固めずにファイルの中身を取得したいと言い換えられますしね。

# Taskはなぜ難しいのか
色々な要因はあると思うのですが、`C#初心者`からすれば前提となる知識が圧倒的に足りていないという要因を個人的には推したい。
というのも`Task`に関する記事はあっても、そこで記述されている事がなんなのかを説明されていない事が多いです。

自分なりに考えた結果、以下の図が`Task`の最低限の構成要素です。下から知識を積み上げていけば、自然と`Task`もわかるはず。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/172223/4665b506-c57d-4401-a30a-f6495851207b.png)

## ジェネリクスについて
下の階層から積み上げていきましょう。
`List<T>`とかでよく見るアレ。なんかわかるようでわからん。そこで例を出してみます。

>色んな型に対応できるような、2つの値を比較して大きい値を返すメソッドをつくってね( ˇωˇ )

```C#:Form1.cs
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();

        var result = Compare(1, 2);
    }
    private int Compare(int x, int y)
    {
        return x > y ? x : y;
    }
    private double Compare(double x, double y)
    {
        return x > y ? x : y;
    }
    private float Compare(float x, float y)
    {
        return x > y ? x : y;
    }
    private short Compare(short x, short y)
    {
        return x > y ? x : y;
    }

………
………
………

}
```
**( ˇωˇ )「正気か？」**
ジェネリクス使ってやれば、あらゆる型に対応可能。

```C#:Form1.cs
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();

        var result = Compare(1, 2);
    }

    //ジェネリクスを使って比較メソッドを実装
    //比較可能な型を指定した時にのみ このメソッドを呼び出せる
    private T Compare<T>(T x, T y) where T : IComparable
    {
        return x.CompareTo(y) > 0 ? x : y;
    }
}
```
まあ要は、`<T>`に対して型はなんでも入れれますよーって認識でよい。

## デリゲートについて
プログラムにおいて、命名する事は悪です。
一々処理に名前つけていったら疲れちゃうので匿名でメソッドを処理できるようにしよう！そうしよう！値とかじゃなくて、メソッド自体を持たせられるようにしようぜ！というので生まれたのが`Delegate`。

```C#:Form1.cs
public partial class Form1 : Form
{
    // SomeDelegate という名前のデリゲート型を定義
    private delegate void SomeDelegate();
    public Form1()
    {
        InitializeComponent();

        //SomeDelegate型の変数にメソッドを代入
        //型を明示的に宣言する必要がある
        SomeDelegate hoge = WriteDatetimeNow;

        //このように代入したメソッドを呼び出す
        hoge.Invoke();
    }

    private void WriteDatetimeNow()
    {
        Console.WriteLine(DateTime.Now);
    }
}
```
イベントに対してイベントハンドラを割り付けるということは頻繁にやると思うのですが、`delegate`はメソッドそのものなので、イベントに対してのイベントハンドラという役割が担えます。なので、基本的には以下のような使い方をします。

```C#:Form1.cs
public partial class Form1 : Form
{
    // SomeDelegate という名前のデリゲート型を定義
    public delegate void SomeDelegate();

    //何らかのイベントを定義
    public event SomeDelegate HogeEvent;

    public Form1()
    {
        InitializeComponent();

        //SomeDelegate型の変数にメソッドを代入
        //型を明示的に宣言する必要がある
        SomeDelegate someDelegate = WriteDatetimeNow;

        //イベントにメソッドを割り付ける
        HogeEvent += someDelegate;
    }
    private void WriteDatetimeNow()
    {
        Console.WriteLine(DateTime.Now);
    }
}
```
あんまり`delegate`って見かけないかもね。

## 定義済みデリゲートについて
こっからがちょっとややこいです。
`Task`を色々な記事で見かける中でなんかよくわかんねーけどこう書けばいいんやなー。というのがこれ。

```C#:Form1.cs
Task.Run(() =>
{
    //何かしらの処理
});
```

`Task.Runメソッド`に、なに渡してんの？
これ、**定義済みデリゲート**を渡しています。

なんじゃそら？

`Delegate`の宣言さえめんどくせえ、もっと簡単に書こうぜ！
で考えられたのが`Action<T>`と`Func<T, TResult>`
こいつらを定義済みデリゲートといいます。
よく使うやつは使いやすいように定義しこうぜってことですね。

とりあえず、`Action<T>`か`Func<T, TResult>`を渡せばいいようなので愚直に`Action<T>`を渡してみる。書いてみる。

```C#:Form1.cs
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }
    private void Form1_Load(object sender, EventArgs e)
    {
        Task.Run(new Action(WriteDateTimeNow));

        //もしくはこっち
        Task.Run(() => WriteDateTimeNow());
    }
    private void WriteDateTimeNow()
    {
        Console.WriteLine(DateTime.Now);
    }
}
```

こういう風に`Task.Run(new Action(WriteDateTimeNow));`
とか`Task.Run(() => WriteDateTimeNow());`
とか名前のついているメソッドも勿論渡せます。
渡せますが、

あーでも、メソッドに名前つけたくないよー！！！
はい。

```C#:Form1.cs
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }
    private void Form1_Load(object sender, EventArgs e)
    {
        Task.Run(() =>
        //{}の中身が名前のないメソッド
        {
            Console.WriteLine(DateTime.Now);
        });
    }
}
```
もはや`delegate型`の変数宣言さえ要らずに匿名メソッドが書けるようになりました( ˇωˇ )
んで、`Action<T>`と`Func<T, TResult>`って単語はよく聞くけどなんか違いわからんのや…(´･ω･｀)
って方、安心してください。

違いは、**「戻り値が void か否か」**だけです。

戻り値が`void型`なら`Action<T>`で、
戻り値が`TResult型`なら`Func<T, TResult>`なだけ。

なるほど、よくわからん！な方の為に、

```C#:Form1.cs
Task.Run(
//この()は何なのか
() =>
{
    //何かしらの処理
});
```
この`()`の中には、本来パラメーターを指定します。
空の括弧は何のパラメーターも送ってないわけです。

つまり、

```C#:Form1.cs
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }
    private void Form1_Load(object sender, EventArgs e)
    {
        //パターン1
        //引数は無し
        //戻り値はvoid
        Action Hoge = () => Console.WriteLine(DateTime.Now);
        Task.Run(Hoge);

        //もしくはこう書く
        Task.Run(() => Console.WriteLine(DateTime.Now));

        //パターン2
        //引数はint型を1つ
        //戻り値はvoid
        Action<int> Fuga = x => Console.WriteLine(x);
        Task.Run(() =>
        {
            var t = 1;
            Fuga(t);
        });

        //もしくはこう書く
        Task.Run(() =>
        {
            var t = 1;
            Console.WriteLine(t);
        });
    }
}
```
戻り値が`void型`でよい場合に使うのが`Action<T>`で、

```C#:Form1.cs
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        //パターン1
        //引数はint型を1つ
        //戻り値はstring型
        Func<int, string> Hoge = (int x) => x.ToString();
        var hoge = Task.Run(() =>
        {
            var x = 1;
            return Hoge(x);
        });

        //もしくはこう書く
        hoge = Task.Run(() =>
        {
            var x = 1;
            return x.ToString();
        });

        //パターン2
        //引数はint型を2つ
        //戻り値はstring型
        Func<int, int, string> Fuga = (x, y) => (x + y).ToString();
        var fuga = Task.Run(() =>
        {
            var t = 1;
            var v = 2;
            return Fuga(t, v);
        });

        //もしくはこう書く
        fuga = Task.Run(() =>
        {
            var t = 1;
            var v = 2;
            return (t + v).ToString();
        });
    }
}
```
戻り値が`void型以外`の場合に使うのが`Func<T>`です。
業務的に使う場合は、名前空間を汚さないように使ったりしますね。

## 非同期処理とは
非同期処理ってなんなんでしょうね。逆に同期処理ってなんでしょう。
`UIスレッド`で処理する事を同期処理、
`UIスレッド以外 (例えば、スレッドプール)`で処理することを私は非同期処理、だと認識しています。
もう少し言うと、コールバック処理をさせていたら非同期処理、ということです。

## スレッドプールについて
まず、スレッドプールへの処理のぶっこみ方法はこれ。

```C#:Form1.cs
Task.Run(() =>
{
    //何かしらの処理
});
```
はい、何回も出てきています。これを書けば何かしらの処理はスレッドプールにぶっこまれます( ˘ω˘)
ちょいちょい出てくるスレッドプールって何なんでしょう。

スレッドプールは、既に作られたスレッドを効率よく使いまわす仕組みの事を言っています。
というのも、スレッドを立てる事は非常に時間が掛かるようなのでそこをパフォーマンスよく処理してやる為に考えられたものですね。

なのでこの`Task.Run`はスレッドを作るんではなくて、既存のスレッドにコールバックをぶちこんでるだけ、という訳です。

で、まあパフォーマンスがよくなったり画面が固まらなくなるのは嬉しいのだけど、`UI`を更新できるのは`UIスレッド`だけなんですわ。

1 ～ 10000までをカウントアップしながら出力するプログラムを愚直に書いてみる。

```C#:Form1.cs
using System;
using System.Windows.Forms;

namespace WindowsFormsApp6
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        private void Form1_Shown(object sender, EventArgs e)
        {
            //至極単純にこうやればよいのでは？
            for (var i = 1; i <= 10000; ++i)
            {
                textBox1.Text = i.ToString();
            }
        }
    }
}
```
これ、期待する結果は得られないんです。
何故なら、全てを同期処理で書いてしまっている為。
全部が終わらないと描画がされない、つまり、何回やったって10000としかでない。

じゃあ非同期で書けばよいのでは？

```C#:Form1.cs
using System;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApp6
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        private void Form1_Shown(object sender, EventArgs e)
        {
            //非同期処理にしてやれば全てうまくいくはず
            Task.Run(() =>
            {
                for (var i = 1; i <= 10000; ++i)
                {
                    textBox1.Text = i.ToString();
                }
            });
        }
    }
}
```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/172223/9ff78349-4396-69e3-c85b-1e82bdaa900f.png)
残念、怒られちゃう。
なんでか。だって**`UI`を更新できるのは`UIスレッド`だけ**だから。
つまり、どうかけばいいのさ？
結構簡単、`Invokeメソッド`内で`UI`の更新を行ってやればよいだけ。

```C#:Form1.cs
using System;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApp6
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        private void Form1_Shown(object sender, EventArgs e)
        {
            Task.Run(() =>
            {
                for (var i = 1; i <= 10000; ++i)
                {
                    //Invokeメソッド内ではUIスレッドに戻してくれる
                    Invoke(new Action(() =>
                    {
                        textBox1.Text = i.ToString();
                    }));
                }
            });
        }
    }
}
```
![非同期8.gif](https://qiita-image-store.s3.amazonaws.com/0/172223/a21e0539-7f25-c9cc-f847-f27b70658047.gif)
諸々の処理はスレッドプールなど、他のスレッドに任せてよいのだけど、`UI`への更新は`UIスレッド`からしか出来ない事は覚えておいた方が捗ります。

## Taskについて
スレッドセーフについてはまああんまし関係ないというか、無い事もないですがどちらかというと並列処理に絡んでくるので今回はスルーします。

漸く本題、`Task`についてさらっといくよ、さらっと。
問1. 以下のコードを実行した場合、出力結果はどうなるでしょーか。

```C#:Form1.cs
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private async void Form1_Load(object sender, EventArgs e)
    {
        Console.WriteLine("A");
        Task.Run(() =>
        {
            Console.WriteLine("B");
        });
        Console.WriteLine("C");
    }
}
```

答えは、
>A
>C
>B

です。

ほげ?
問2. んじゃこれなら？

```C#:Form1.cs
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private async void Form1_Load(object sender, EventArgs e)
    {
        Console.WriteLine("A");
        await Task.Run(() =>
        {
            Console.WriteLine("B");
        });
        Console.WriteLine("C");
    }
}
```
答えは、
>A
>B
>C

です( ˇωˇ )

問1では`Aを出力`した後に`Taskを作る(つくるだけ)`、次に`Cを出力`して`Task`が実行される。
問2では`Aを出力`した後に`Taskを作ったうえで待つ`、次に`Cを出力`して終了。

ここらへんごちゃってる人多いと思うけどまとめるよ。
①`awaitキーワード`を付けたらその処理が終わるまで待ってくれる。
②`awaitキーワード`を使用するのであれば、`asyncキーワード`をメソッドの戻り値の前につけてやる
③`awaitキーワード`で待てる処理は`Task/Task<T>`型

# 問題提起の解説

```C#:Form1.cs
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApp6
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        /// <summary>
        /// ロードイベント
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Form1_Load(object sender, EventArgs e)
        {
            //巨大なファイルを読み込むつもりならここはあえてawaitキーワードを外すべき
            //UIスレッドで処理をさせると固まってしまうのでスレッドプールに処理を投げる
            //Task.Runメソッド内でawait を使用しているので、asyncキーワードを付ける
            Task.Run(async () =>
            {
                var builder = new StringBuilder();
                var csvFileName = @"C:\temp\hoge.csv";

                //無限ループ
                while (true)
                {
                    byte[] result;
                    builder.Clear();

                    //ファイルオープン
                    using (var file = new FileStream(csvFileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
                    {
                        result = new byte[file.Length];

                        //バイナリデータを非同期的に読み込む
                        //全部を読み込み終わるまで待っとくで～
                        await file.ReadAsync(result, 0, (int)file.Length);
                    }
                    
                    foreach (char c in result)
                    {
                        if (char.IsLetterOrDigit(c) || char.IsWhiteSpace(c))
                        {
                            builder.Append(c);
                        }
                    }

                    //UIを更新できるのはUIスレッドだけなので Invoke でUIスレッドに一旦処理を戻してやる
                    Invoke(new Action(() =>
                    {
                        textBox1.Text = builder.ToString();
                    }));

                    //タイマーイベントの代わり
                    //1秒待つ
                    await Task.Delay(1000);
                }
            });
        }
    }
}
```

このページを開いた時には分からなかったとしても、ここまで読み進めてきたらなんとなく読めるようになったのではないのでしょうか。
ちなみに、`async/awaitキーワード`が使えるのは`.NET Framework4.5以上`をターゲットフレームワークに指定しているからです。

# まとめ
色々ごちゃごちゃ書いたんですが、`Task`を理解する為には多くの前提知識を必要としているなとは常々感じていました。じゃあ何が分かっていれば`Task`の理解へと繋がるのか？ということを振り返ってみて、それを形容化してみました。

ダダダダと書いたので分かりにくい点などあれば、是非ご指摘をお願いします。