TAP (Task-based Asynchronous Pattern) 非同期メソッドのガイドライン

Task-based Asynchronous Pattern (TAP) と呼ばれる、最新の .NET Framework で推奨される非同期メソッド実装におけるデザインガイドラインをまとめました。にわかなのでマサカリ歓迎します。

[Async および Await を使用した非同期プログラミング (C# および Visual Basic)](https://msdn.microsoft.com/ja-jp/library/Hh191443.aspx) も参照してください。
また、下記では [.NETで非同期ライブラリを正しく実装する ](http://www.infoq.com/jp/articles/Async-API-Design) の内容を踏まえています。

# メソッド名は `～Async` もしくは `～TaskAsync` とする

非同期操作を実行するメソッドは名前に `Async` サフィックスをつけます。
もし既に `Async` で終わる `Task` を返却しない同名メソッド（`IAsyncResult` などを返すメソッド）が存在している場合は `TaskAsync` とします。

```csharp
var result = await httpClient.GetAsync("http://google.co.jp/");
```

>  TAP の非同期操作は、操作名の後に `Async` サフィックスが付きます。たとえば、取得 (Get) 操作の場合は `GetAsync` になります。 既に `Async` サフィックスの付いたメソッド名を含むクラスに TAP メソッドを追加する場合は、代わりに `TaskAsync` サフィックスを使用します。 たとえば、既にクラスに `GetAsync` メソッドが含まれている場合は、`GetTaskAsync` という名前を使用します。
> [タスク ベースの非同期パターン (TAP)](https://msdn.microsoft.com/ja-jp/library/Hh873175%28v=VS.110%29.aspx)

## 例外

イベントハンドラで非同期処理を実行する場合は従来のイベントハンドラ名で構いません。

```csharp
private async void btnExecute_Click(object sender, EventArgs e)
{
    await foo.BarAsync();
    DoSomething();
}
```

# 戻り値は `Task` もしくは `Task<T>` とする

非同期操作を実行するメソッドの戻り値は `Task` もしくは `Task<T>` を戻り値とします。
戻り値に `void` は使用しません。

## 例外

イベントハンドラで非同期処理を実行する場合は `void` で構いません。
イベントハンドラ以外で `void` の非同期メソッドを作成してはいけません。

```csharp
private async void btnExecute_Click(object sender, EventArgs e)
{
    await foo.BarAsync();
    DoSomething();
}
```

(参考) [asyncの落とし穴Part3, async voidを避けるべき100億の理由](http://neue.cc/2013/10/10_429.html)

# ライブラリとして提供するメソッドで `Task.Run` は使用しません

`Task.Run` を使用するかどうかは呼び出し元が決定できますし、するべきです。

すなわち、ライブラリが非同期メソッドを提供するのは主に内部でネイティブ非同期メソッドを使用する場合です。

```csharp
public async Task<bool> NanikaHandan()
{
    var result = await _httpClient.GetAsync(_toaruUri).ConfigureAwait(false);
    var str = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
    return str == _nanika;
}
```

ネイティブ非同期メソッドはスレッドプールを使った別スレッドによる非同期処理を目的としていません。
I/Oなどの待ちに対してスレッドを空けて同時実効性を高めることが目的のため、`Task.Run` を使用する非同期処理とは本質的に目的が異なります。

## 例外

[.NETで非同期ライブラリを正しく実装する](http://www.infoq.com/jp/articles/Async-API-Design) 内で触れられています。

# ライブラリとして提供するメソッドで `ConfigureAwait(false)` を呼び出します

呼び出し元の UI で `Task.Wait()` を使用せざるを得ない場合にデッドロックになる現象を回避するためです。
これが有名な `ConfigureAwait(false)` 地獄です。有名なのかな……？

冒頭の記事のほか [HttpClient詳解、或いは非同期の落とし穴について](http://www.slideshare.net/neuecc/httpclient) でも解説されています。

また、Web の場合（かつ処理の完了を待たないような書き方をするケースで）は `SynchronizationContext` が `null` になって `NullReferenceException` が発生するという問題もあります。こちらは [asyncの落とし穴Part2, SynchronizationContextの向こう側](http://neue.cc/2013/07/02_412.html) で解説されています。

ちなみに、この辺は過去に識者たちが改めて解説してくださいました。
[ConfigureAwait - Togetterまとめ](http://togetter.com/li/815086)

## (2016/02/06 追記) `ConfigureAwait(false)` をつけまくるかどうかについて

デッドロックを防ぐには以下の様な呼び出し方をする方法もあることを教えてもらいました。

```csharp
Task.Run(async () => await hoge.FugaAsync()).Wait();
```

このようにすると `hoge.FugaAsync()` が戻そうとする先の `SynchronizationContext` が `Task.Run` によって生成されたスレッドプールからのスレッドに対して向くため、`Wait()` するスレッドと互いに待ち合う形にならず、デッドロックを防止できます。

以下の様なメリット・デメリットが考えられます。

* ライブラリ側が `ConfigureAwait(false)` をつけまくる
    * [○] 呼び出し元が `Wait()` を呼んでしまってもデッドロックしない
    * [△] `ConfigureAwait(false)` のつけ忘れがあるとデッドロックしてしまう
    * [○] 同期コンテキストを戻さない分、パフォーマンスが良い
    * [×] ソースコードにノイズが増えて見づらくなる（横に長くなるし）
* 呼び出し元で `Task.Run` して回避する
    * [○] ライブラリ側が書きやすくなる
    * [×] パフォーマンス面では不利になる
    * [×] 呼び出し元が `Wait()` する際にきちんと理解して回避しないとデッドロックする

要はライブラリ側で考慮してあげるか、利用側が考慮して呼び出すかという責務の問題になります。

ライブラリ開発者、利用者がそれぞれどのような人たちなのか、考えられる `Wait()` せざるを得ない状況がどのくらいあるのか、等によって決めると良いかと思います。

### (参考) Wait() をなるべく無くす工夫

#### `catch` 句で `await` が使えない問題

C#5.0以前では `catch` 句で `await` を使用することができません。

```csharp
catch (HogeException ex)
{
    // C#5.0 以前では await できない
    await foo.HandleErrorAsync();
    throw;
}
```

そのため `Wait()` を呼び出さざるを得ないということもあるのではないでしょうか。

```csharp
catch (HogeException ex)
{
    // 同期版メソッドが用意されていない場合など
    foo.HandleErrorAsync().Wait();
    throw;
}
```

しかし、`ExceptionDispatchInfo` を使用することで非同期メソッドを同期的に待機しなくて済むように工夫できるケースがあります。

```csharp
ExceptionDispatchInfo info = null;
try
{
    ....
}
catch (HogeException ex)
{
    // 例外情報を捕捉
    info = ExceptionDispatchInfo.Capture(ex);
}

if (info != null)
{
    // catch の外で処理する
    await foo.HandleErrorAsync();
    // Throw() で例外情報を保ったまま再スローできる
    info.Throw();
}
```

# 可能であれば `CancellationToken` を受け取ります

非同期処理をキャンセル可能とするために `CancellationToken` を受け取るオーバーロードを作成します。

```csharp
public async Task<bool> NanikaHandan(CancellationToken cancellationToken)
{
    var result = await _httpClient.GetAsync(_toaruUri, cancellationToken).ConfigureAwait(false);
    var str = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
    return str == _nanika;
}
```

また、`CancellationToken` を受け取らないオーバーロードから `CancellationToken` を受け取るオーバーロードを呼び出す場合には `CancellationToken.None` を使用できます。

```csharp
public Task<bool> NanikaHandan()
{
    return NanikaHandan(CancellationToken.None);
}
```

`Task.Run` を使わない場合、自身でキャンセル要求に対する中断を実装することは少ないかもしれませんが、呼び出し元からキャンセルが要求されたかを確認して処理を中断したい場合には `CancellationToken.ThrowIfCancellationRequested()` メソッドを使用します。

# 末尾の処理が非同期メソッドの呼び出しで、かつ唯一の場合は await を使用せずにそのまま返します

```csharp
public async Task HogeAsync()
{
    await fuga.DoSomethingAsync();
}
```

上記は下記のようにします。
不要な `Awaitor` 呼び出しが挟まらなくなります。

```csharp
public Task HogeAsync()
{
    return fuga.DoSomethingAsync();
}
```

ちょうど `IEnumerable<T>` を `foreach` で列挙して `yield return` するなら、その `IEnumerable<T>` をそのまま戻せばいいじゃない、と同じような話かと。

## (2016/02/06 追記) とは言え…

実際には以下の様な修正が発生した時に変更量が多くなってしまうので微妙なところです。

```csharp
public Task HogeAsync()
{
    // ここに piyo.DoSomethingAsync(); を追加したい

    return fuga.DoSomethingAsync();
}
```

↓

```csharp
public async Task HogeAsync() // この行が Diff に :sob:
{
    await piyo.DoSomethingAsync();

    await fuga.DoSomethingAsync(); // この行も diff に :sob:
}
```

ということで、私は普段は「別のオーバーロードを呼び出す場合」くらいでしか「`await` せずに直接 `Task` を返却」ということをやっていません。別のオーバーロード呼び出しであれば前後に処理が増えることはほとんど発生しないためです。

## (2016/02/12 追記) using を使う場合などは注意が必要

うっかりやりかねないので念のため書いておきます。

下記のようなコードは意図しない動作をするので注意が必要です。

```csharp
public Task HogeAsync()
{
    using (var foo = new Foo())
    {
        return foo.FugaAsync(); // foo.FugaAsync() の終了を待たずに foo.Dispose() が呼ばれる
    }
}
```

これは以下と等価ではありません。

```csharp
public async Task HogeAsync()
{
    using (var foo = new Foo())
    {
        await foo.FugaAsync(); // foo.Dispose() は foo.FugaAsync() の待機後に呼ばれる
    }
}
```

それぞれ（概ね）以下のように展開されるためです。

```csharp
// 1 つ目のパターン

public Task HogeAsync()
{
    // using は try-finally で Dispose するコードのシンタックスシュガー
    Foo foo = null;
    try
    {
        foo = new Foo();
        return foo.FugaAsync(); // Task を返す
    }
    finally
    {
        // Task を返した直後に呼ばれる
        if (foo != null)
            foo.Dispose();
    }
}

// 2 つ目のパターン

public Task HogeAsync()
{
    // using 内で await するとコンパイラが実行順序をヨシナニしてくれる
    var foo = new Foo();
    // 実際は Awaiter 経由だし SynchronizationContext.Current が null のケースとかあるけど、イメージとしてはこんな感じ
    return foo.FugaAsync().ContinueWith(t =>
    {
        foo.Dispose();
    }, TaskScheduler.FromCurrentSynchronizationContext());
}
```

なお、もはや非同期の話ですらないですが、`yield return` についても `using` 内で同じ話になるので注意。

```csharp
public IEnumerable<FooBar> GetFooBars()
{
    using (hoge)
    {
        return fooBars;
    }
}

// ↑と↓は同義ではない

public IEnumerable<FooBar> GetFooBars()
{
    using (hoge)
    {
        foreach (var fooBar in fooBars)
        {
            yield return fooBar;
        }
    }
}
```