57

ネットワーク I/O を実行するメソッドの設計の問題に直面しています (再利用可能なライブラリの場合)。この質問を読みました

API設計におけるc#5 await/asyncパターン

また、私の問題に近い他のもの。

問題は、非同期メソッドと非非同期メソッドの両方を提供したい場合、これらをどのように設計する必要があるかということです。

たとえば、メソッドの非非同期バージョンを公開するには、次のようなことをする必要があります

public void DoSomething() {
  DoSomethingAsync(CancellationToken.None).Wait();
}

素晴らしいデザインではないと感じています。両方のバージョンを提供するために、パブリック メソッドにラップできるプライベート メソッドを定義する方法について、(たとえば) 提案をお願いします。

4

3 に答える 3

71

最もメンテナンスしやすいオプションが必要な場合は、asyncAPI のみを提供します。この API は、ブロッキング呼び出しを行ったり、スレッド プール スレッドを使用したりせずに実装されます。

本当に と 同期 API の両方が必要asyncな場合は、保守性の問題が発生します。本当に 2 回実装する必要があります。1 回asyncと 1 回は同期です。これらの方法はどちらもほぼ同じに見えるため、最初の実装は簡単ですが、2 つの別々のほぼ同じ方法になるため、メンテナンスが問題になります。

async特に、単にまたは同期の「ラッパー」を作成するための適切で簡単な方法はありません。Stephen Toub がこの件に関する最良の情報を提供しています。

  1. 同期メソッドの非同期ラッパーを公開する必要がありますか?
  2. 非同期メソッドの同期ラッパーを公開する必要がありますか?

(両方の質問に対する短い答えは「いいえ」です)

ただし、重複した実装を回避したい場合に使用できるハックがいくつかあります。最良のものは、通常、ブール引数 hackです。

于 2013-02-14T13:54:32.017 に答える
3

マークとスティーブン(クリアリー)の両方に同意します。

(ところで、私はスティーブンの答えへのコメントとしてこれを書き始めましたが、長すぎることが判明しました。これを答えとして書いてもよいかどうか教えてください。 「1つの最良の答えを提供する」という精神で、スティーブンの答えにそれを)。

それは本当に「依存」します。Marc が言ったように、DoSomethingAsync がどのように非同期であるかを知ることが重要です。「sync」メソッドが「async」メソッドを呼び出して「待機」することに意味がないことには、誰もが同意します。これは、ユーザー コードで実行できます。別のメソッドを持つことの唯一の利点は、実際のパフォーマンスが向上することと、内部的には異なり、同期シナリオに合わせて調整された実装を持つことです。これは、「非同期」メソッドがスレッドを作成している (またはスレッドプールからスレッドを取得している) 場合に特に当てはまります。その下で 2 つの「制御フロー」を使用するものになり、その同期ルックスで「約束」が実行されるようになります。呼び出し元のコンテキスト。実装によっては、これには並行性の問題さえあるかもしれません。

また、OPが言及している集中的なI / Oのような他のケースでは、2つの異なる実装を持つ価値があるかもしれません。ほとんどのオペレーティング システム (確かに Windows) には、2 つのシナリオに合わせて調整された I/O のさまざまなメカニズムがあります。カーネルのオーバーヘッド (重要ではありませんが、null ではない) (結局のところ、簿記、ディスパッチなどを行う必要があります)、および同期操作のより直接的な実装。コードの複雑さも大きく異なります。特に、複数の操作が実行/調整される関数では顕著です。

私がすることは次のとおりです。

  • 典型的な使用法とシナリオのいくつかの例/テストがあります
  • どの API バリアントが使用されているか、どこで、どのように測定されているかを確認します。「純粋な同期」バリアントと「同期」の間のパフォーマンスの違いも測定します。(API全体ではなく、いくつかの典型的なケースを選択)
  • 測定に基づいて、追加コストがそれだけの価値があるかどうかを判断します。

これは主に、2 つの目標が何らかの形で互いに対照的であるためです。保守可能なコードが必要な場合、明らかな選択は、非同期/待機の観点から同期を実装することです (またはその逆) (または、さらに良いのは、非同期バリアントのみを提供し、ユーザーに「待機」させることです)。パフォーマンスが必要な場合は、2 つの関数を異なる方法で実装して、異なる基礎となるメカニズム (フレームワークまたは OS から) を利用する必要があります。単体テストの観点から、実際に API をどのように実装するかによって違いが生じることはないと思います。

于 2013-02-21T10:42:25.627 に答える
1

私は同じ問題に遭遇しましたが、非同期メソッドに関する 2 つの単純な事実を使用して、効率と保守性の間の妥協点を見つけることができました。

  • await を実行しない非同期メソッドは同期です。
  • 同期メソッドのみを待機する非同期メソッドは同期です。

これは例で示す方が良いです:

//Simple synchronous methods that starts third party component, waits for a second and gets result.
public ThirdPartyResult Execute(ThirdPartyOptions options)
{
    ThirdPartyComponent.Start(options);
    System.Threading.Thread.Sleep(1000);
    return ThirdPartyComponent.GetResult();
}

このメソッドの保守可能な同期/非同期バージョンを提供するために、3 つのレイヤーに分割されました。

//Lower level - parts that work differently for sync/async version.
//When isAsync is false there are no await operators and method is running synchronously.
private static async Task Wait(bool isAsync, int milliseconds)
{
    if (isAsync)
    {
        await Task.Delay(milliseconds);
    }
    else
    {
        System.Threading.Thread.Sleep(milliseconds);
    }
}

//Middle level - the main algorithm.
//When isAsync is false the only awaited method is running synchronously,
//so the whole algorithm is running synchronously.
private async Task<ThirdPartyResult> Execute(bool isAsync, ThirdPartyOptions options)
{
    ThirdPartyComponent.Start(options);
    await Wait(isAsync, 1000);
    return ThirdPartyComponent.GetResult();
}

//Upper level - public synchronous API.
//Internal method runs synchronously and will be already finished when Result property is accessed.
public ThirdPartyResult ExecuteSync(ThirdPartyOptions options)
{
    return Execute(false, options).Result;
}

//Upper level - public asynchronous API.
public async Task<ThirdPartyResult> ExecuteAsync(ThirdPartyOptions options)
{
    return await Execute(true, options);
}

ここでの主な利点は、変更される可能性が最も高い中間レベルのアルゴリズムが 1 回だけ実装されるため、開発者は 2 つのほぼ同一のコードを維持する必要がないことです。

于 2018-11-06T18:17:01.923 に答える