1

フレームワークのユーザーがノンブロッキング関数 (別名「コルーチン」) を備えた言語として C# を使用できるフレームワークを拡張しようとしています。私の最初の設計yield returnでは、関数の戻り値としてステートメントと IEnumerator を使用しました。

ただし、別のyield関数を呼び出そうとすると(さらに、他の関数が値を返す必要がある場合)、これはかなりエラーが発生しやすく、使いにくいです。

そのため、私はコルーチンを使用asyncawaitて提供するというアイデアをいじっています。結果として得られる構文は、はるかに楽しいものになるでしょう。

私たちのフレームワークでは、これらのノンブロッキング スクリプトが 2 つ同時に実行されないようにする必要があるため、SynchonizationContextすべてのアクションを自分でスケジュールする独自のスクリプトを作成しました。それは魅力のように機能します。

さらに興味深いのは、私たちの設計のもう 1 つの重要な部分は、失敗した場合に「現在の」実行中の機能を続行しない何らかの「チェック機能」をユーザーが登録できることです。チェック関数は、ノンブロッキング関数が再開するたびに実行する必要があります。

たとえば、次のようになります。

async Task DoSomething()
{
    var someObject = SomeService.Get(...);
    using (var check = new SanityCheckScope())
    {
        check.StopWhen(() => someObject.Lifetime < 0);

        ...

        await SomeOtherStuff();
        // when execution comes back in here, the StopWhen - condition above should be
        // evaluated and the Task should not be scheduled if its true.

        ...
    }
}

SomeOtherStuffそれによって呼び出された非同期関数が再開するたびに、登録された条件をチェックしDoSomething、いずれかの条件が true の場合は停止する必要があります。

これを実装するために、SynchonizationContext は登録された関数 ( を介して渡されるCallContext.LogicalSetData) をチェックし、true が返された場合はタスクをスケジュールしません。

ここで厄介な問題が発生します。関数SomeOtherStuffが次のようになっているとします。

async Task SomeOtherStuff()
{
    using (var check = new SanityCheckScope())
    {
        // register my own check functions
        await Whatever();
    }
}

例ではSomeOtherStuff、独自のチェック関数を登録します。の後に true の場合はawait Whatever()、当然、SomeOtherStuff関数のみを停止する必要があります。( SomeOtherStuff が を返す場合、が戻り値として使用されTask<XXX>ても問題ないと仮定しましょう。)default(XXX)

どうすればこれにアプローチできますか?await SomeOtherStuff();元の関数の の後に来る Action にたどり着けません。どちらかの先頭にフックやコールバックがないようですawait(とにかく意味がありません)。

別のアイデアは、タスクを「スケジュールしない」のではなく、例外をスローすることです。-blockの代わりに (または追加して)、usingtry/catch を記述します。ただし、 DoSomethingのチェック機能がインナーの後に失敗した場合、await Whatever();どのように停止するかという問題が発生しましたDoSomething(一緒にSomeOtherStuff)?

だから私がこれをすべてスクラッチして私のものに戻る前にIEnumerator..これが/フレームワークyield returnでできるかどうか誰かが考えましたか?asyncawait

4

2 に答える 2

2

私の推奨事項は、TPL Dataflow を使用することです。言語機能を悪用すればするほどasync(またはさらに言えばIEnumerable)、コードの保守性が低下します。

それは言った...

awaitカスタム awaitablesの周りにいくつかのフックがあります。Stephen Toub はそれらについてのブログを持っており、Jon Skeet は彼のeduasync シリーズでいくつかの詳細をカバーしています。

ただし、メソッドからカスタム awaitable を返すことはできないasyncため、このアプローチは、すべてのawaits が「チェック」動作に「オプトイン」する必要があることを意味します。

async Task DoSomething()
{
  var someObject = SomeService.Get(...);
  using (var check = new SanityCheckScope())
  {
    check.StopWhen(() => someObject.Lifetime < 0);
    await SomeOtherStuff().WithChecks();
  }
}

チェックが失敗したときのセマンティクスがどうあるべきかは、私には明確ではありません。あなたの質問から、チェックが失敗すると、そのメソッドを中止したいだけのように聞こえるので、チェックはモニターや条件変数よりもコード契約に似ています。また、スコープはそのメソッドにのみ適用され、そのメソッドから呼び出されるメソッドには適用されないように思えます。

返された をカプセル化しTaskTaskCompletionSource<T>別のTask. asyncカスタム awaiter は、メソッドの継続を実行する前にチェックを実行します。

注意すべきもう1つのことは、「スコープ」がどのように機能するかです。次の状況を考慮してください。

async Task DoSomething()
{
  using (var check = new SanityCheckScope())
  {
    check.StopWhen(...);

    await SomeOtherStuff();

    await MoreOtherStuff();
  }
}

またはさらに楽しい:

async Task DoSomething()
{
  using (var check = new SanityCheckScope())
  {
    check.StopWhen(...);

    await SomeOtherStuff();

    await Task.WhenAll(MoreOtherStuff(), MoreOtherStuff());
  }
}

適切に機能するためには、スコープがAsyncLocal<T>(私のブログで説明されている)を利用する必要があると思います。

于 2013-06-11T11:47:21.267 に答える
0

独自の実装を検討しましたSynchronizationContextか? 独自のものを実装するか (意味のある SynchronizationContext が既にインストールされていないと仮定して)、既存のコンテキストの周りにラッパーをインストールすることができます。

await 構文は、継続を SynchronizationContext にポストし (await がそうしないように構成されていないと仮定します)、それをインターセプトできるようにします。もちろん、非同期コールバックは SynchronizationContext にポストされるため、継続が呼び出された正確な瞬間を検出するのは簡単ではない可能性があるため、コンテキストにポストされたすべてのコールバックに対してサニティ チェックが発生する可能性があります。

SanityCheckScope は、チェック スコープが SynchronizationContext にネストされた方法で登録されていることを確認できます。明らかに、クラスの IDisposable 部分が親スコープの登録を解除して復元します。これは、複数の並列子スコープを持つことができる状況がない場合にのみ機能します。

実行を停止する唯一の方法は、私が見る限り、何らかの例外をスローすることです。

于 2013-06-11T10:09:39.803 に答える