6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

C# の Channel を学ぶ (2)

Posted at

前回 C# の Channel を学ぶ (1) ではチャネルの基礎を学んだ。今回はキャンセルされた場合や、タイムアウトの実装を見てみる。CancellationToken をAPIがサポートしているので簡単だ。

今回も

のブログの内容を実施している。

Timeout

タイムアウトのケースでは、ReadAsync()CancellationToken を受け付けるAPIがあるので、それを使うと、キャンセルが発生したときに OperationCanceledException が発生するようになっている。

public async Task ExecuteAsync()
{
    var joe = CreateMessenger("Joe", 10);
    var cts = new CancellationTokenSource();
    cts.CancelAfter(TimeSpan.FromSeconds(5));

    try
    {
        await foreach (var item in joe.ReadAllAsync(cts.Token))
            Console.WriteLine(item);
        Console.WriteLine("Joe sent all of his messages.");
    }
    catch (OperationCanceledException)
    {
        Console.WriteLine("Timeout happens. 5 sec.");
    }
}

image.png

Quit Channel

上記のように、Exception を発生させるのではなく、チャネルが途中で中断されたという風にコードを書いてみよう。実際にキャンセルが発生した場合に、token.IsCancellationRequested を使って、チャネルを Complete するようにすると、うまくそのチャネルだけが中断されて、終了するという感じでコードを書くことが出来る。これは、Producer 側で処理を入れておくことになる。

static ChannelReader<string> CreateMessenger(string msg, int count, CancellationToken token = default)
{
    var ch = Channel.CreateUnbounded<string>();
    var rnd = new Random();

    Task.Run(async () =>
    {
        for (int i = 0; i < count; i++)
        {
            if (token.IsCancellationRequested)
            {
                await ch.Writer.WriteAsync($"{msg} says good bye!");
                break;
            }
            await ch.Writer.WriteAsync($"[{DateTime.Now}] {msg} {i}");
            await Task.Delay(TimeSpan.FromSeconds(rnd.Next(3)));
        }
        ch.Writer.Complete();

    });

    return ch.Reader;
}
public async Task ExecuteAsync()
{
    var cts = new CancellationTokenSource();
    var joe = CreateMessenger("Joe", 10, cts.Token);
    cts.CancelAfter(TimeSpan.FromSeconds(5));

    await foreach (var item in joe.ReadAllAsync())
        Console.WriteLine(item);
}

image.png

Web Search

最後に総まとめで、Webサイトにアクセスする場合を想定したキャンセレーショントークンの使い方を見てみよう。Webへのリクエストをシュミレートするため、ランダムなスピードで処理が実行される Search メソッドを作成する。検索の結果は、チャネルに書き込まれるというのを想定している。

private async Task Search(string source, string term, Channel<string> ch,  CancellationToken token)
{
    await Task.Delay(TimeSpan.FromSeconds(new Random().Next(5)), token);
    await ch.Writer.WriteAsync($"Result from {source} for {term}", token);
}

3つのサイトにアクセスしているが、トータルタイムで、タイムアウトが設定されている。タイムアウトになると例外が発生する。

public async Task ExecuteAsync()
{
    var ch = Channel.CreateUnbounded<string>();
    var term = "teststrone";
    var token = new CancellationTokenSource(TimeSpan.FromSeconds(3)).Token;
    
    var search1 = Search("Google", term, ch, token);
    var search2 = Search("Quora", term, ch, token);
    var search3 = Search("Wikipedia", term, ch, token);

    try
    {
        for (int i = 0; i < 3; i++)
            Console.WriteLine(await ch.Reader.ReadAsync(token));

        Console.WriteLine("All searchs have completed.");
    } 
    catch (OperationCanceledException)
    {
        Console.WriteLine("Timeout!");
    }

}

予想通りタイミングによって、タイムアウトになったり、ならなかったりする。

image.png
image.png

まとめ

C#の CancellationToken が強力なので、実装がシンプルでとても良い感じ。次回はチャネルの学習の最終回。

6
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?