C#5.0の非同期関数と通常の関数の間の線をぼかす


10

最近、C#5.0の驚くべき非同期待機パターンを十分に理解できていないようです。あなたは私の人生のどこにいましたか?

単純な構文には本当に感激していますが、小さな難しさがあります。私の問題は、非同期関数の宣言が通常の関数とはまったく異なるということです。他の非同期関数で待機できるのは非同期関数だけなので、古いブロックコードを非同期に移植しようとすると、変換しなければならない関数のドミノ効果があります。

人々はこれをゾンビの蔓延と呼んでいます。asyncがコードに噛み付くと、ますます大きくなります。移植プロセスは難しくありません。async宣言をスローし、戻り値をでラップするだけTask<>です。しかし、古い同期コードを移植するときに、これを何度も繰り返すのは面倒です。

両方の関数タイプ(asyncとplain old sync)がまったく同じ構文を持っていると、はるかに自然に思えます。これが事実であった場合、移植はゼロの労力で済み、2つの形式を簡単に切り替えることができます。

私はこれらのルールに従えばこれはうまくいくと思います:

  1. 非同期関数はasync宣言を必要としなくなります。それらの戻り値の型をにラップする必要はありませんTask<>。コンパイラは、コンパイル時に非同期関数を単独で識別し、必要に応じてTask <>ラッピングを自動的に実行します。

  2. 非同期関数の呼び出しを忘れることはありません。非同期関数を呼び出したい場合は、それを待つ必要があります。私はとにかくファイアアンドフォーゲットをほとんど使用していません。クレイジーレースコンディションやデッドロックのすべての例は、常にそれらに基づいているようです。私はそれらが私たちが活用しようとしている同期の考え方とあまりに混乱していて「連絡がない」と思います。

  3. 本当に忘れずに生きられないのなら、それのための特別な構文があります。いずれにせよ、それは私が話している単純な統一構文の一部にはなりません。

  4. 非同期呼び出しを示す必要がある唯一のキーワードはawaitです。待機している場合、呼び出しは非同期です。そうでない場合、呼び出しは従来の同期になります(覚えておく必要はありません)。

  5. コンパイラーは非同期関数を自動的に識別します(関数には特別な宣言がなくなるため)。ルール4では、これを非常に簡単に行うawaitことができます。関数の内部に呼び出しがある場合、関数は非同期です。

これでうまくいきますか?または私は何かを逃していますか?この統一された構文ははるかに流動的で、ゾンビの蔓延を完全に解決できます。

いくつかの例:

// assume this is an async function (has await calls inside)
int CalcRemoteBalanceAsync() { ... }

// assume this is a regular sync function (has no await calls inside)
int CalcRemoteBalance() { ... }

// now let's try all combinations and see what they do:

// this is the common synchronous case - it will block
int b1 = CalcRemoteBalance();

// this is the common asynchronous case - it will not block
int b2 = await CalcRemoteBalanceAsync();

// choosing to call an asynchronous function in a synchronous manner - it will block
// this syntax was used previously for async fire-and-forget, but now it's just synchronous
int b3 = CalcRemoteBalanceAsync();

// strange combination - it will block since it's calling a synchronous function
// it should probably show a compilation warning though
int b4 = await CalcRemoteBalance();

注:これは、SO での興味深い関連ディスカッションの続きです。


3
あなたはいつもあなたの非同期操作を待っていますか?...あなたはすぐにそれらを焼成した後、これをしない私に教えてください
ジミー・ホッファ

1
また、非同期の優れた点の1つは、awaitすぐに行う必要がないことです。のようなことができますvar task = FooAsync(); Bar(); await task;。あなたの提案でこれをどのように行いますか?
スビック2013

3
SOは議論をしていますか? 私のBFG-3000はどこにありますか
ロバートハーベイ

2
@talkol並列プログラミングは卑猥だと思いますか?それは、控えめに言っても、あなたがについて話しているとき、興味深い展望ですasync。私はそれがasync- の大きな利点の1つだと思いますawait。これにより、非同期操作を簡単に作成できます(最も単純な「Aの開始、Aの待機、Bの開始、Bの待機」の方法だけではありません)。そして、この目的のために特別な構文がすでにありますawait。それはと呼ばれます。
2013

1
@svick haha​​、私たちはそこに行きました:)私は並列プログレッシブを卑猥だとは思いませんが、非同期-待機でそれを行うのはそうだと思います。Async-awaitは、ブロッキングの代償を払うことなく同期状態を維持するための構文シュガーです。すでに並行して考えている場合は、別のパターンを使用することをお勧めします
talkol 2013

回答:


9

あなたの質問は、あなたがリンクしたSO質問ですでに回答されています。

async / awaitの目的は、レイテンシの高い操作が多い世界でコードを簡単に記述できるようにすることです。操作の大部分は、待ち時間が長くありません

ときWinRTのは、最初に出てきた、設計者は、彼らが操作が非同期になるだろうされた決定方法を説明しました。彼らは、50ms以上かかるものはすべて非同期であり、残りのメソッドは通常の非同期メソッドであることを決定しました。

非同期にするために、いくつのメソッドを書き直す必要がありましたか?それらの約10パーセント。他の90%はまったく影響を受けませんでした。

エリックリッペルトは、ワンサイズのアプローチを採用しないことを選択した理由をかなりの技術的詳細で説明し続けます。彼は基本的に、asyncそれawaitは継続渡しスタイルの部分的な実装であり、すべてのケースに適合するようにそのようなスタイルを最適化することは難しい問題だと言っています。


SOの質問とこの質問の大きな違いに注意してください。SOは、なぜすべてを非同期にしないのかと尋ねます。ここでは、これはお勧めしません。10%非同期にすることをお勧めします。同じ構文を使用するだけです。より近い構文を使用することには、これらの変更によるドミノの影響を受けることなく、非同期である10%をより簡単に変更できるという利点があります
talkol

なぜasyncゾンビの蔓延を引き起こすのか、私には少し不明確です。メソッドが他の10個のメソッドを呼び出したとしてもasync、最上位のメソッドを呼び出す必要はありませんか?
Robert Harvey

6
現在のコードの100%が同期しているとしましょう。これで、非同期に変更したいDBをクエリする単一の内部リーフレベル関数があります。次に、非同期にするために、呼び出し元を非同期にし、呼び出し元を非同期にする必要があります。もちろん、チェーン全体が(コードの同期設計を維持するため、または戻り値を渡すために)待たれる場合について話している
talkol
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.