92314


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

提示コードは非同期で`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]


CC BY-SA 4.0
2022-11-19T09:18:12.607
92312
2022-11-20T00:13:28.693
55177
question
c#
非同期の無限ループ処理を終了させる一番シンプルなやり方が知りたい


正しくは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]


CC BY-SA 4.0
2022-11-19T13:01:41.373
92314
2022-11-20T00:13:28.693
2022-11-20T00:13:28.693
4236
4236
92312
answer


sayuri さんのおっしゃるとおり、`CancellationToken` を使用します。

以下、CancellationToken の実装例になります。

[code]

    public class Account
    {
        private CancellationTokenSource _cancellationTokenSource = new();
        
        public void Cancel()
        {
            _cancellationTokenSource.Cancel();
        }
    
        public void setStreamTimeLine()
        {    
            Task.Run( async () =>
            { 
                try
                {
                    var token = _cancellationTokenSource.Token;
                    while (true)
                    {
                        token.ThrowIfCancellationRequested();
                        JsonData.StreamJson json = await client.StreamingHomeTimeLine_Async(token);
                        Console.WriteLine(json.eventType);
                    }
                }
                catch (OperationCanceledException)
                {
                    Console.WriteLine("Canceled.");
                }
    
                Console.WriteLine("終了");
            });
        }
    }
    
[/code]

[code]

    public async Task<JsonData.StreamJson?> StreamingHomeTimeLine_Async(CancellationToken token)
    {   
        :
        while (true)
        {
            token.ThrowIfCancellationRequested();
            var message = await streamReader.ReadLineAsync().WaitAsync(token);
            :
    
[/code]

[code]

    // Thread.Sleep(3000);
    Global.account[0].Cancel();
    Global.account.Remove(Global.account[0]);
    
[/code]

.NET の Async 関数のほとんどは CancellationToken を引数に持っており、その await
をキャンセルできるようになっているため、同様にキャンセルさせます。  
`ReadLineAsycn()` のように対応していないものもありますが、.NET6 では
`ReadLineAsycn().WaitAsync(token)` とすることで擬似的にキャンセルできるようになります。  
.NET6 より前ですと別の対応方法が必要になります。詳細は以下を参考にしてください。  
<https://stackoverflow.com/questions/28626575/can-i-cancel-streamreader-
readlineasync-with-a-cancellationtoken>  
ちなみに .NET7 からは `ReadLineAsycn(token)` が使用できるようになりました。

あと、「Accountが削除されるときに」とありますので、IDisposable インターフェイスを実装してタスクを終了させるようにすると良いと思います。


CC BY-SA 4.0
2022-11-19T13:19:05.090
92315
2022-11-19T13:24:05.940
2022-11-19T13:24:05.940
14817
14817
92312
answer