21

私はasyncc#5.0に含まれる新機能に関する新しい発表をフォローしています。私は、継続渡しスタイルと、新しいc#コンパイラがEric Lippertの投稿からのこのスニペットのようなコードに変換することについての基本的な理解を持っています:

async void ArchiveDocuments(List<Url> urls)
{
  Task archive = null;
  for(int i = 0; i < urls.Count; ++i)
  {
    var document = await FetchAsync(urls[i]);
    if (archive != null)
      await archive;
    archive = ArchiveAsync(document);
  }
}

一部の言語は、call-with-current-continuation(callcc)を介してネイティブに継続を実装していることは知っていますが、それがどのように機能するのか、または正確に何をするのかはよくわかりません。

だからここに質問があります:アンダースらの場合。弾丸を噛み、/特殊なケースcallccの代わりにc#5.0で実装することにした場合、上記のスニペットはどのようになりますか?asyncawait

4

3 に答える 3

29

元の答え:

あなたの質問は、私が理解しているように、「タスクベースの非同期に特化して「待機」を実装する代わりに、call-with-current-continuation のより一般的な制御フロー操作が実装されていたらどうなるか?」です。

さて、まず「await」が何をするのか考えてみましょう。「await」は type の式を取り、Task<T>awaiter を取得し、現在の継続で awaiter を呼び出します。

await FooAsync()

効果的になる

var task = FooAsync();
var awaiter = task.GetAwaiter();
awaiter.BeginAwait(somehow get the current continuation);

callccここで、引数としてメソッドを受け取り、現在の継続でメソッドを呼び出す演算子があるとします。それは次のようになります。

var task = FooAsync();
var awaiter = task.GetAwaiter();
callcc awaiter.BeginAwait;

言い換えると:

await FooAsync()

にすぎない

callcc FooAsync().GetAwaiter().BeginAwait;

それはあなたの質問に答えていますか?


更新 #1:

コメンターが指摘しているように、以下の回答は、非同期/待機機能の「テクノロジープレビュー」バージョンからのコード生成パターンを想定しています。機能のベータ版では実際にはわずかに異なるコードを生成しますが、論理的には同じです。現在の codegen は次のようなものです。

var task = FooAsync();
var awaiter = task.GetAwaiter();
if (!awaiter.IsCompleted)
{
    awaiter.OnCompleted(somehow get the current continuation);
    // control now returns to the caller; when the task is complete control resumes...
}
// ... here:
result = awaiter.GetResult();
// And now the task builder for the current method is updated with the result.

これはやや複雑で、すでに計算された結果を「待っている」場合を処理することに注意してください。待機している結果が実際にメモリにキャッシュされている場合は、呼び出し元に制御を渡して中断したところから再開するという厳格な手順をすべて実行する必要はありません。

したがって、「await」と「callcc」の間の接続は、プレビュー リリースの場合ほど単純ではありませんが、基本的に awaiter の「OnCompleted」メソッドで callcc を実行していることは明らかです。必要がない場合は callcc を実行しません。


更新 #2:

この答えとして

https://stackoverflow.com/a/9826822/88656

Timwi が指摘するように、call/cc と await のセマンティクスはまったく同じではありません。「真の」call/cc では、呼び出しスタック全体を含むメソッドの継続全体を「キャプチャ」するか、同等にプログラム全体を継続渡しスタイルに書き直す必要があります。

"await" 機能は "cooperative call/cc" に似ています。継続は、「待機の時点で現在のタスクを返すメソッドが次に何をしようとしているのか」をキャプチャするだけです。タスクを返すメソッドの呼び出し元が、タスクの完了後に何か面白いことをしようとしている場合、その継続をタスクの継続として自由にサインアップできます。

于 2010-11-01T16:42:52.327 に答える
4

私は継続の専門家ではありませんが、async/await と call/cc の違いを説明することに挑戦します。もちろん、この説明は、私が call/cc と async/await を理解していることを前提としています。それにもかかわらず、ここに行く...

C# 'async' を使用すると、その特定のメソッドの特別なバージョンを生成するようにコンパイラに指示します。これは、その状態をヒープ データ構造にボトル詰めする方法を理解するため、「実際のスタックから削除」して後で再開できます。非同期コンテキスト内では、"await" は、コンパイラによって生成されたオブジェクトを使用して状態をボトルアップし、タスクが完了するまで "実際のスタック" から降りるという点で "call/cc" に似ています。ただし、状態をボトルアップできるようにするのはコンパイラによる async-method の書き換えであるため、await は async コンテキスト内でのみ使用できます。

第一級の call/cc では、言語ランタイムはすべてのコードを生成し、現在の継続を call-continuation-function にまとめることができるようにします (async キーワードを不要にします)。call/cc は引き続き await のように動作し、現在の継続状態 (スタック状態を考えてください) がボトルに入れられ、関数として呼び出された関数に渡されます。これを行う 1 つの方法は、「スタック」フレームの代わりに、すべての関数呼び出しにヒープ フレームを使用することです。(「stackless python」や多くのスキーム実装のように、「stackless」と呼ばれることもあります) もう 1 つの方法は、call/cc ターゲットを呼び出す前に、「実際のスタック」からすべてのデータを削除し、ヒープ データ構造に詰め込むことです。 .

外部関数 (DllImport を考えてください) への呼び出しがスタック上で混在している場合、いくつかのトリッキーな問題が発生する可能性があります。これが、彼らが async/await 実装を採用した理由だと思います。

http://www.madore.org/~david/computers/callcc.html


C# では、これらのメカニズムを使用するために関数を「async」としてマークする必要があるため、この async キーワードが多くのライブラリの多くの関数に広がるウイルスになるのではないかと思います。これが発生した場合。彼らは最終的に、このコンパイラー書き換えベースの非同期モデルではなく、VM レベルでファーストクラスの call/cc を実装する必要があることに気付くかもしれません。時間だけが教えてくれます。ただし、現在の C# 環境では便利なツールであることは間違いありません。

于 2010-12-16T17:36:20.490 に答える
3

ちょっと無意味だと思います。スキーム インタープリタは、ローカルの状態をヒープにキャプチャするステート マシンを使用して call/cc を実装することがよくあります。これは、まさにC# 5.0 (より正確には C# 2.0 の反復子) が行うことでもあります。彼らcall/cc を実装しました。彼らが思いついた抽象化は非常に洗練されています。

于 2010-11-01T15:43:24.963 に答える