C# 非同期メソッドのタイムアウト

非同期メソッドのタイムアウトをさせたかったのでコード試してみた。

最初は、`Task.Wait()` を使用しようとしたけど、結局`Task.Result` で待ち合わせるので、結局待ち合わせてしまう。`Task.WhenAny` を使えばうまくいく。`Task.WhenAny` は複数のタスクのうち、どれかが終われば、先に進むタスク。その性質を利用している。

自分用のメモ


```csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace PollingSpike
{
    class Program
    {
        static void Main(string[] args)
        {
            new Program().MainAsync().GetAwaiter().GetResult();
        }

        private async Task MainAsync()
        {
            var timeout = TimeSpan.FromSeconds(5);
            
            Console.WriteLine($"Timeout: {timeout.TotalMilliseconds}");
            var task = pollingMessageAsync();
            if (await Task.WhenAny(task, Task.Delay(timeout)) == task)
            {
                Console.WriteLine($"Result: {task.Result.ToString()}");
            } else
            {
                Console.WriteLine("Result1 timeout");
            }
            var task2 = pollingMessageTimeOutAsync();
            if (await Task.WhenAny(task2, Task.Delay(timeout)) == task2)
            {
                Console.WriteLine($"Result2: {task2.Result.ToString()}");
            } else
            {
                Console.WriteLine("Result2 timeout");
            }
            Console.ReadLine();
            Console.WriteLine("Task3 has been started");
            var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
            var token = cts.Token;
            try
            {
                var task3 = await pollingMEssageTimeOutByCancellationToken(token);
                Console.WriteLine($"Result3 this is not called {task3}");
                
            } catch (TaskCanceledException e)
            {
                Console.WriteLine($"Task3 has been canceled by timeout {e.Message}");
                Console.ReadLine();
            } 
        }

        private async Task<string> pollingMessageAsync()
        {
            await Task.Delay(TimeSpan.FromSeconds(3));
            return "Finish! 3 sec";
        }

        private async Task<string> pollingMessageTimeOutAsync()
        {
            await Task.Delay(TimeSpan.FromSeconds(10));
            return "Finish! 10 sec";

        }

        private async Task<string> pollingMEssageTimeOutByCancellationToken(CancellationToken token)
        {
            await Task.Delay(TimeSpan.FromSeconds(10), token);
            return "Finish! 10 sec with cancellation token";
        }
    }
}


```
# 追記 2017/12/31 17:20

Cancellation token がよさげということなので、そのパターンも実装してみました。普通のインターフェイスも実装しているから
それがいいのかな。ちなみに、Cancellation Token を実装すると、メソッド側で、Cancellation token が呼ばれたかをチェックする必要がありますが、今回はループ系で使うのでそれでいいのかもですね。ご指摘いただきありがとうございました。

# Resource

* [Asynchronously wait for Task<T> to complete with timeout
](https://stackoverflow.com/questions/4238345/asynchronously-wait-for-taskt-to-complete-with-timeout)
* [Task.WhenAny Method](https://msdn.microsoft.com/en-us/library/system.threading.tasks.task.whenany(v=vs.110).aspx)
* [CancellationTokenSource クラス](https://msdn.microsoft.com/ja-jp/library/system.threading.cancellationtokensource(v=vs.110).aspx)