非同期の無限ループ処理を終了させる一番シンプルなやり方が知りたい


##### 提示コードの処理内容

提示コードは非同期で`http`通信でストリーミングして`json`を取得する処理なのです。

##### やりたい事

`setStreamTimeLine()`関数の所属するクラス`Account`が削除されるときに`Task.Run()`の中身の`while`文とその内部の`StreamingHomeTimeLine_Async(this);`内部の無限ループを停止してほしいのですがこれを実装するにはどうしたらいいのでしょうか？現状のように一応実装出来ているのですが`非同期処理らしいやり方`等存在するのでしょうか？

##### 知りたい事

`非同期処理らしいやり方`で無限ループを抜ける処理を実装する方法が知りたい

##### 現状

`isStreamStop`変数の`true`にすることで無限ループから抜ける処理を実装出来ています。

##### ソースコード

[code]

    /*##################################################################################################################
             * タイムライン無限 ループ
             * ###################################################################################################################*/
            public void setStreamTimeLine()
            {    
                Task.Run( async () =>
                { 
                    while(isStreamStop == false)
                    {
                        JsonData.StreamJson json = await client.StreamingHomeTimeLine_Async(this);
    
                        Console.WriteLine(json.eventType);
                    }
    
                    Console.WriteLine("終了");
                });
            }
    
    
[/code]

[code]

            /*##################################################################################################################
             * フォロータイムライン　ストリーミング
            ###################################################################################################################*/
            public async Task<JsonData.StreamJson?> StreamingHomeTimeLine_Async(Account acc)
            {   
                var response = await client.GetStreamAsync("https://" + instance + "/api/v1/streaming/user");
                var streamReader = new StreamReader(response);
    
               
                while(acc.isStreamStop == false)
                {
                    var message = await streamReader.ReadLineAsync();
                    var eventType = message.Replace("event: ", "");
                    Console.WriteLine("eventType    " + eventType);
    
                    switch (eventType)
                    {
                        case "update":
                        {
                            var data = await streamReader.ReadLineAsync();
                            data = data.Replace("data: ", "");
                            var json = JsonConvert.DeserializeObject<JsonData.Post>(data);
    
                            return new JsonData.StreamJson(JsonData.StreamJson.EventType.Update,json,null,null);
                        }
                        break;
    
                        case "notification":
                        {
                            var data = await streamReader.ReadLineAsync();
                            data = data.Replace("data: ", "");
                            var json = JsonConvert.DeserializeObject<JsonData.Notice>(data);
    
                            return new JsonData.StreamJson(JsonData.StreamJson.EventType.Notice,null,json,null);
                        }
                        break;
    
                        case "delete":
                        {
                            var data = await streamReader.ReadLineAsync();
                            data = data.Replace("data: ", "");
    
                            return new JsonData.StreamJson(JsonData.StreamJson.EventType.Delete,null,null,data);
                        }
                        break;
    
                        default:
                        {
                        }
                        break;
                    }
                }
                return new JsonData.StreamJson(JsonData.StreamJson.EventType.Invalid, null, null, null);
    
                //return null;
            }
    
    
    
[/code]

##### フォーム

[code]

            /*##################################################################################################################
             * インスタンス登録メニューをクリック
            ###################################################################################################################*/
            private void Register_Click(object sender, EventArgs e)
            {
                domain = new InputDomain_Form();
                domain.Show();
    
    //                        Thread.Sleep(3000);
                Global.account[0].isStreamStop = true;
                Global.account.Remove(Global.account[0]);
    
            }
    
    
[/code]




正しくはdameoさんも紹介されている`CancellationToken`を使うことです。  
これを正しく機能させるには、すべての非同期メソッド呼び出しで`CancellationToken`の受け渡しを必ず行う必要があります。しかし、`CancellationToken`なしのオーバーロードも用意されているので渡し忘れていてもコンパイルエラーにならず気づき辛いです。また同期呼び出しが紛れ込んでいてもダメです。その他にもいろいろあり割とツラミがあります。

* * *

「シンプルなやり方が知りたい」とのことですので、別解を用意しました。`Stream`を保持しておき、それを`Close`してしまうことです。`Close`されれば`ReadAsync()`などはすべて失敗し例外が投げられますので、それを`while`の外側で`catch`すれば処理を抜けることができます。

[code]

    Stream activeStream;
    public async Task<JsonData.StreamJson?> StreamingHomeTimeLine_Async(Account acc) {   
        var response = await client.GetStreamAsync("https://" + instance + "/api/v1/streaming/user");
        activeStream = response;
        ...
    }
    
    private void Register_Click(object sender, EventArgs e) {
        domain = new InputDomain_Form();
        domain.Show();
        activeStream?.Close();
        Global.account.Remove(Global.account[0]);
    }
    
[/code]




正しくはdameoさんも紹介されている`CancellationToken`を使うことです。  
これを正しく機能させるには、すべての非同期メソッド呼び出しで`CancellationToken`の受け渡しを必ず行う必要があります。しかし、`CancellationToken`なしのオーバーロードも用意されているので渡し忘れていてもコンパイルエラーにならず気づき辛いです。また同期呼び出しが紛れ込んでいてもダメです。その他にもいろいろあり割とツラミがあります。

* * *

「シンプルなやり方が知りたい」とのことですので、別解を用意しました。`Stream`を保持しておき、それを`Close`してしまうことです。`Close`されれば`ReadAsync()`などはすべて失敗し例外が投げられますので、それを`while`の外側で`catch`すれば処理を抜けることができます。

[code]

    Stream activeStream;
    public async Task<JsonData.StreamJson?> StreamingHomeTimeLine_Async(Account acc) {   
        var response = await client.GetStreamAsync("https://" + instance + "/api/v1/streaming/user");
        activeStream = response;
        ...
    }
    
    private void Register_Click(object sender, EventArgs e) {
        domain = new InputDomain_Form();
        domain.Show();
        activeStream?.Close();
        Global.account.Remove(Global.account[0]);
    }
    
[/code]

