C# async / await / Taskの使い方（備忘録）

# きっかけ

https://qiita.com/takmot/items/83ca6350b10af959f3db
上記記事で書いた以下コード

```C#
using Microsoft.AspNetCore.SignalR;
using System.Threading.Tasks;
using SignalRChat.Hubs;

namespace SignalRChat
{
    public class app
    {
        private IHubContext<ChatHub> Hub
        {
            get;
            set;
        }
        public app(IHubContext<ChatHub> hub)
        {
            Hub = hub; // ハブコンテキストを取得
        }
        public async Task pushMessage(string msg)
        {
            await Hub.Clients.All.SendAsync("pushMessage", msg); // メッセージ送信
        }
    }
}
```

`pushMessage`コールの際にawaitを付けないと、`この呼び出しを待たないため、現在のメソッドの実行は、呼び出しが完了するまで続行します。呼び出しの結果に 'await' 演算子を適用することを検討してください。 `と警告が出ます。
このTaskの扱いに困ったため、色々記事は読んでみたものの、完全には理解できなかった。
とりあえず、現在の自分の理解と対処について記載しようと思います。

（補足..）
警告が出るのは`async Task`のメソッドコール時で、
SendAsyncは、awaitを付けずにコールしても警告は出ませんでした。
しかし、awaitを付けないと`async void`パターンと同様にタスクの終了が待てない、投げっぱなしになることなのでよろしくないようです。


# async / await / Task

上記警告が出るため、Taskに対してawaitを付ける。
awaitを使用しているメソッドにはasyncを付ける必要があるため、asyncを付ける。（下記例`method_A()`）
そのメソッドをコールしようとすると同様に警告が出る。
そのため、コール元もawait/asyncを付ける。
`async void`を使えばawaitはいらなくなるが、`async void`は使うなと言われている。

```C#
static async Task method_A()
{
    await Task.Run(() => {
        Console.WriteLine($"task contents : {Thread.CurrentThread.ManagedThreadId}");
    });
    Console.WriteLine("method_A Completed");
}
static async Task method_B()
{
    await method_A();
    Console.WriteLine("method_B Completed");
}
```

`async void`を使わない理由としては以下になります。

- awaitで待ち受けできなくなり、そのためスレッドの終了を知ることが出来ない、投げっぱなし（fire and forget）になる。
- 例外を捕獲できない

そこで、以下のような実装にしました。
コメントは実行順番と、実行されるスレッドです。
`ContinueWith`でタスク終了後にタスクの状態を見ています。

```C#
using System;
using System.Threading;
using System.Threading.Tasks;

namespace task_sample
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine($"Main start : {Thread.CurrentThread.ManagedThreadId}");                      // 1 (スレッドA)
            AsyncMethod().ContinueWith((task) => {
                Console.WriteLine($"task.status {task.Status} : {Thread.CurrentThread.ManagedThreadId}");   // 6 (スレッドC)
            });
            Console.WriteLine($"Main end : {Thread.CurrentThread.ManagedThreadId}");                        // 4 (スレッドA)

            Console.ReadLine();
        }

        static async Task AsyncMethod()
        {
            Console.WriteLine($"AsyncMethod start : {Thread.CurrentThread.ManagedThreadId}");               // 2 (スレッドA)
            await Task.Run(() => {
                Console.WriteLine($"task contents : {Thread.CurrentThread.ManagedThreadId}");               // 3 (スレッドB)
            });
            Console.WriteLine($"AsyncMethod end : {Thread.CurrentThread.ManagedThreadId}");                 // 5 (スレッドB)
        }
    }
}
```

`task.status`で以下のようなタスクのステータスが確認できます。

- TaskStatus.RanToCompletion : 正常終了した
- TaskStatus.Canceled : キャンセルされた
- TaskStatus.Faulted : 例外が発生した

### Taskが戻り値を持っている場合

task.Resultで戻り値を取得できます。

```C#
using System;
using System.Threading;
using System.Threading.Tasks;

namespace task_sample
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine($"Main start : {Thread.CurrentThread.ManagedThreadId}");                      // 1 (スレッドA)
            AsyncMethod().ContinueWith((task) => {
                Console.WriteLine($"task.status {task.Status} : {Thread.CurrentThread.ManagedThreadId}");   // 6 (スレッドC)
                Console.WriteLine($"task.Result {task.Result} : {Thread.CurrentThread.ManagedThreadId}");
            });
            Console.WriteLine($"Main end : {Thread.CurrentThread.ManagedThreadId}");                        // 4 (スレッドA)

            Console.ReadLine();
        }

        static async Task<string> AsyncMethod()
        {
            Console.WriteLine($"AsyncMethod start : {Thread.CurrentThread.ManagedThreadId}");               // 2 (スレッドA)
            await Task.Run(() => {
                Console.WriteLine($"task contents : {Thread.CurrentThread.ManagedThreadId}");               // 3 (スレッドB)
            });
            Console.WriteLine($"AsyncMethod end : {Thread.CurrentThread.ManagedThreadId}");                 // 5 (スレッドB)

            return "Task Completed";
        }
    }
}
```

# Task.Run()

`Task.Run()`は、
何かの通信で受信待ち→受信処理を繰り返す
無限ループを使用するような場合だけ使用するイメージで理解しました。

以下記事でUDPパケット受信タスクを作りました。
https://qiita.com/takmot/items/6287911115575ab84b05

# 参考記事
https://qiita.com/acple@github/items/8f63aacb13de9954c5da
https://qiita.com/inew/items/0126270bca99883605de
https://qiita.com/hiki_neet_p/items/d6b3addda6c248e53ef0
https://qiita.com/4_mio_11/items/f9b19c04509328b1e5c1
https://qiita.com/rawr/items/5d49960a4e4d3823722f
http://neue.cc/2013/10/10_429.html
https://stackoverflow.com/questions/62444555/can-i-omit-await-of-clients-all-sendasync-in-net-core-signalr