【C#】非同期で処理を実施する方法

# 結論
`Task.Run`、`async`、`await`を使います。

```C#:非同期ソース例(Task.Run)
Console.WriteLine("処理を開始");

var task = Task.Run(() =>
{
    //時間がかかる処理
    for (int i = 0; i < 5; i++)
    {
        Console.WriteLine("非同期処理中...");
        Thread.Sleep(1000);
    }
});

//非同期処理が完了するまで待機
while (!task.IsCompleted)
{
    Console.WriteLine("処理中...");
    Thread.Sleep(1000);
}

Console.WriteLine("処理が終了");
```

以下に事例別に解説します。
# 解説
結論の非同期ソース例を実行すると下記のような出力になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2519245/8bda0101-50db-314c-4846-3afb0074378b.png)
## 非同期処理を実施しながら他の処理をしたい場合
`Task.Run`を使います。
こちらは結論の非同期処理ソース例を使用することで再現できます。
細かく解説していきます。
改めて解説するソースコードは下記になります。

```C#:非同期処理を実施しながら他の処理をしたい場合のソース例
Console.WriteLine("処理を開始");

var task = Task.Run(() =>
{
    //時間がかかる処理
    for (int i = 0; i < 5; i++)
    {
        Console.WriteLine("非同期処理中...");
        Thread.Sleep(1000);
    }
});

//非同期処理が完了するまで待機
while (!task.IsCompleted)
{
    Console.WriteLine("処理中...");
    Thread.Sleep(1000);
}

Console.WriteLine("処理が終了");
```

非同期で実行している箇所は下記になります。

```C#:非同期実行している箇所
var task = Task.Run(() =>
{
    //時間がかかる処理
    for (int i = 0; i < 5; i++)
    {
        Console.WriteLine("非同期処理中...");
        Thread.Sleep(1000);
    }
});
```

`Task.Run()`を使用することでタスク(非同期処理)を実行することができます。
タスクの状態は`task`変数に格納されています。`task`変数は`Task`型です。
`Task`型で個人的によく使うプロパティと関数は下記になります。

| 種類 | 名前 | 内容 |
|:-:|:-:|:-:|
| プロパティ  | IsCompleted  |  タスクが完了したかどうかを示す値を取得します。 |
|  関数 | Delay  | 非同期で待機します。(Thread.Sleepの非同期版)  |
|  関数 |  Run | タスクを実行してTask型のオブジェクトを返します。  |
そして、非同期中に実行している箇所は下記になります。

```C#:非同期処理と同時進行している箇所
while (!task.IsCompleted)
{
    Console.WriteLine("処理中...");
    Thread.Sleep(1000);
}

Console.WriteLine("処理が終了");
```

ここでは`task`変数の`IsCompleted`プロパティを確認して`false`(実行中)であれば`処理中...`と出力しています。そして、`IsCompleted`プロパティを確認して`true`(実行終了)になればwhileループを抜けて`処理が終了`と出力しています。

## 関数を非同期で実行したい場合
`async`と`await`を使用します。
下記はWinFormアプリケーションで現在時刻を非同期で更新するソースコードです。

```C#:ソース例
private CancellationTokenSource tokenSource;
private CancellationToken cancelToken;

private async void buttonStart_Click(object sender, EventArgs e)
{
    //現在時刻の更新処理を開始する
    tokenSource = new CancellationTokenSource();
    cancelToken = tokenSource.Token;
    await UpdateDateTimeAsync(cancelToken);
}

private void buttonEnd_Click(object sender, EventArgs e)
{
    //現在時刻の更新処理を停止する。
    tokenSource.Cancel();
}

private async Task UpdateDateTimeAsync(CancellationToken cancelToken)
{
    //停止リクエストが来たら終了する。
    while (!cancelToken.IsCancellationRequested)
    {
        labelDateTimeNow.Text = DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss");
        await Task.Delay(1000);
    }
}
```
実行した結果は下記の画像です。非同期処理なのでフォームの位置を移動できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2519245/7959cc58-ee45-1c21-df94-5b507e4d7b19.png)

`現在時刻更新開始`ボタンを押下すると`buttonStart_Click`関数が実行されます。
`buttonStart_Click`関数では`UpdateDateTimeAsync`関数を非同期処理で実行を開始しています。
関数に`async`をつけることで`await`を実施できるようになります。`await`を関数の前につけるとその関数を非同期で実施している間は呼び出し関数に処理を返します。そしてその関数の非同期処理が終了した段階で`await`後のソースコードを実行します。
ソース箇所は下記になります。

```C#:ソース箇所
private async void buttonStart_Click(object sender, EventArgs e)
{
    //現在時刻の更新処理を開始する
    tokenSource = new CancellationTokenSource();
    cancelToken = tokenSource.Token;
    await UpdateDateTimeAsync(cancelToken);
}
```

`現在時刻更新終了`ボタンを押下すると`buttonEnd_Click`関数が実行されます。
`buttonEnd_Click`関数では`buttonStart_Click`関数で実行を開始した`UpdateDateTimeAsync`関数に対してキャンセルリクエストを送信しています。
ソース箇所は下記になります。

```C#:ソース箇所
private void buttonEnd_Click(object sender, EventArgs e)
{
    //現在時刻の更新処理を停止する。
    tokenSource.Cancel();
}
```

`UpdateDateTimeAsync`関数ではフォームのラベルを1秒おきに現在時刻に更新しています。
もし、実行している途中にキャンセルリクエストが届いている場合はwhileループを抜けて非同期処理を終了しています。
ソース箇所は下記になります。

```C#:ソース箇所
private async Task UpdateDateTimeAsync(CancellationToken cancelToken)
{
    //停止リクエストが来たら終了する。
    while (!cancelToken.IsCancellationRequested)
    {
        labelDateTimeNow.Text = DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss");
        await Task.Delay(1000);
    }
}
```

以上です。

# 非同期処理に関する参考サイト

https://www.fenet.jp/dotnet/column/language/2460/

https://zenn.dev/vatscy/articles/ba2263bdfadfeb805379

https://mslgt.hatenablog.com/entry/2017/10/08/091841#20171008_stop_operation_from_outside_task_run