59

アーキテクチャの下位レベルでasync/awaitを使用している場合、async / await呼び出しを最後まで「バブルアップ」する必要がありますか?基本的に各レイヤーに新しいスレッドを作成しているため、非効率的ですか(非同期呼び出し各レイヤーの非同期関数、またはそれは実際には重要ではなく、単にあなたの好みに依存していますか?

EFで非同期メソッドを使用できるように、EF6.0-alpha3を使用しています。

私のリポジトリはそのようなものです:

public class EntityRepository<E> : IRepository<E> where E : class
{
    public async virtual Task Save()
    {
        await context.SaveChangesAsync();
    }
}

今、私のビジネスレイヤーはそのようなものです:

public abstract class ApplicationBCBase<E> : IEntityBC<E>
{
    public async virtual Task Save()
    {
        await repository.Save();
    }
}

そしてもちろん、UIのメソッドは、呼び出し時に同じパターンに従います。

これは:

  1. 必要
  2. パフォーマンスにマイナス
  3. 好みの問題

これが別々のレイヤー/プロジェクトで使用されていない場合でも、同じクラスでネストされたメソッドを呼び出す場合は、同じ質問が当てはまります。

    private async Task<string> Dosomething1()
    {
        //other stuff 
        ...
        return await Dosomething2();
    }
    private async Task<string> Dosomething2()
    {
        //other stuff 
        ...
        return await Dosomething3();
    }
    private async Task<string> Dosomething3()
    {
        //other stuff 
        ...
        return await Task.Run(() => "");
    }
4

2 に答える 2

59

アーキテクチャの下位レベルでasync/awaitを使用している場合、async / await呼び出しを最後まで「バブルアップ」する必要がありますか?基本的に各レイヤーに新しいスレッドを作成しているため、非効率的ですか(非同期呼び出し各レイヤーの非同期関数、またはそれは実際には重要ではなく、単にあなたの好みに依存していますか?

この質問は、誤解のいくつかの領域を示唆しています。

まず、非同期関数を呼び出すたびに新しいスレッドを作成する必要はありません。

次に、非同期関数を呼び出しているという理由だけで、非同期メソッドを宣言する必要はありません。すでに返されているタスクに満足している場合は、非同期修飾子を持たないメソッドからタスクを返すだけです。

public class EntityRepository<E> : IRepository<E> where E : class
{
    public virtual Task Save()
    {
        return context.SaveChangesAsync();
    }
}

public abstract class ApplicationBCBase<E> : IEntityBC<E>
{
    public virtual Task Save()
    {
        return repository.Save();
    }
}

これ、ごくわずかな理由でステートマシンが作成される必要がないため、わずかに効率的ですが、さらに重要なのは、より単純なことです。

メソッドの最後でaまたは、を待機している単一のawait式があり、それ以上の処理を行わない非同期メソッドは、async/awaitを使用せずに記述した方がよいでしょう。したがって、この:TaskTask<T>

public async Task<string> Foo()
{
    var bar = new Bar();
    bar.Baz();
    return await bar.Quux();
}

次のように書く方が良いです:

public Task<string> Foo()
{
    var bar = new Bar();
    bar.Baz();
    return bar.Quux();
}

(理論的には、作成されるタスクにごくわずかな違いがあるため、呼び出し元が継続を追加できるものに違いがありますが、ほとんどの場合、違いに気付くことはありません。)

于 2013-03-19T15:37:04.700 に答える
28

基本的に各レイヤーに新しいスレッドを作成しているので非効率的ですか(各レイヤーに非同期関数を非同期で呼び出しますか、それとも実際には重要ではなく、好みに依存しますか?

いいえ。非同期メソッドは必ずしも新しいスレッドを使用するわけではありません。この場合、基になる非同期メソッド呼び出しはIOバウンドメソッドであるため、実際には新しいスレッドは作成されません。

これは:

1. necessary

操作を非同期に保ちたい場合は、非同期呼び出しを「バブル」する必要があります。ただし、これは、スタック全体で非同期メソッドを一緒に構成することを含め、非同期メソッドを十分に活用できるため、実際に推奨されます。

2. negative on performance

いいえ。前述したように、これによって新しいスレッドが作成されることはありません。ある程度のオーバーヘッドがありますが、その多くは最小限に抑えることができます(以下を参照)。

3. just a matter of preference

これを非同期にしておきたい場合は違います。スタック全体で物事を非同期に保つためにこれを行う必要があります。

さて、パフォーマンスを向上させるためにできることがいくつかあります。ここ。非同期メソッドをラップするだけの場合は、言語機能を使用する必要はありませんTask。:を返すだけです。

public virtual Task Save()
{
    return repository.Save();
}

repository.Save()メソッドはすでに-を返します-Taskでラップバックするためだけにそれを待つ必要はありませんTask。これにより、メソッドの効率がいくらか向上します。

また、「低レベル」の非同期メソッドを使用ConfigureAwaitして、呼び出し元の同期コンテキストが必要にならないようにすることもできます。

private async Task<string> Dosomething2()
{
    //other stuff 
    ...
    return await Dosomething3().ConfigureAwait(false);
}

await これにより、呼び出し元のコンテキストについて心配する必要がない場合、それぞれに関連するオーバーヘッドが大幅に削減されます。await「外部」はUIのコンテキストをキャプチャするため、これは通常、「ライブラリ」コードで作業する場合に最適なオプションです。ライブラリの「内部」動作は通常、同期コンテキストを気にしないため、それをキャプチャしないことが最善です。

最後に、あなたの例の1つに注意します。

private async Task<string> Dosomething3()
{
    //other stuff 
    ...
    // Potentially a bad idea!
    return await Task.Run(() => "");
}

内部的に、Task.Runそれ自体が非同期ではないものの周りに「非同期を作成」するために使用している非同期メソッドを作成している場合は、同期コードを非同期メソッドに効果的にラップしています。これThreadPoolスレッドを使用しますが、そうしているという事実を「隠す」ことができ、事実上APIを誤解させる可能性があります。多くの場合Task.Run、最高レベルの呼び出しのために呼び出しをそのままにして、非同期IOまたは以外のアンロードの手段を本当に利用できる場合を除いて、基になるメソッドを同期したままにしておくことをお勧めしTask.Runます。(これは常に正しいとは限りませんが、を介して同期コードにラップされTask.Run、async / awaitを介して返される「非同期」コードは、多くの場合、欠陥のある設計の兆候です。)

于 2013-03-19T15:36:22.417 に答える