10

私はWindows8プロジェクトの非同期サービスをいじっていますが、このサービスの非同期呼び出しがいくつかありますが、一度に1回だけ呼び出す必要があります。

 public async Task CallThisOnlyOnce()
 {
      PropagateSomeEvents();

      await SomeOtherMethod();

      PropagateDifferentEvents();
 }

非同期呼び出しをlockステートメントにカプセル化できないため、AsyncLockパターンを使用することを考えましたが、思ったよりも次のようなものを試してみた方がよいでしょう。

 private Task _callThisOnlyOnce;
 public Task CallThisOnlyOnce()
 {
      if(_callThisOnlyOnce != null && _callThisOnlyOnce.IsCompleted)
         _callThisOnlyOnce = null;

      if(_callThisOnlyOnce == null)
         _callThisOnlyOnce = CallThisOnlyOnceAsync();

      return _callThisOnlyOnce;
 }

 private async Task CallThisOnlyOnceAsync()
 {
      PropagateSomeEvents();

      await SomeOtherMethod();

      PropagateDifferentEvents();
 }

したがって、呼び出しCallThisOnlyOnceAsyncは同時に1回だけ実行され、複数の待機者が同じタスクにフックすることになります。

これはこれを行うための「有効な」方法ですか、それともこのアプローチにはいくつかの欠点がありますか?

4

2 に答える 2

7

タスクは複数の待機者を持つことができます。ただし、Damien が指摘したように、提案されたコードには深刻な競合状態があります。

メソッドが呼び出されるたびに (同時にではなく) コードを実行する場合は、 を使用しますAsyncLock。コードを 1 回だけ実行する場合は、 を使用しますAsyncLazy

提案されたソリューションは、複数の呼び出しを結合しようとし、コードがまだ実行されていない場合はコードを再度実行します。これはよりトリッキーであり、解決策は必要な正確なセマンティクスに大きく依存します。1 つのオプションを次に示します。

private AsyncLock mutex = new AsyncLock();
private Task executing;

public async Task CallThisOnlyOnceAsync()
{
  Task action = null;
  using (await mutex.LockAsync())
  {
    if (executing == null)
      executing = DoCallThisOnlyOnceAsync();
    action = executing;
  }

  await action;
}

private async Task DoCallThisOnlyOnceAsync()
{
  PropagateSomeEvents();

  await SomeOtherMethod();

  PropagateDifferentEvents();

  using (await mutex.LockAsync())
  {
    executing = null;
  }
}

でこれを行うことも可能ですInterlockedが、そのコードは見苦しくなります。

PS 私はAsyncEx ライブラリAsyncLockに、、、AsyncLazyおよびその他のasync準備が整ったプリミティブを持っています。

于 2012-11-23T16:50:25.897 に答える
4

複数のスレッドが関係している可能性がある場合、このコードは非常に「際どい」ように見えます。

一例です(他にもあると思います)。_callThisOnlyOnce現在は次のnullとおりであると仮定します。

Thread 1                                                          Thread 2

public Task CallThisOnlyOnce()
{
  if(_callThisOnlyOnce != null && _callThisOnlyOnce.IsCompleted)
     _callThisOnlyOnce = null;

  if(_callThisOnlyOnce == null)
                                                                   public Task CallThisOnlyOnce()
                                                                   {
                                                                     if(_callThisOnlyOnce != null && _callThisOnlyOnce.IsCompleted)
                                                                        _callThisOnlyOnce = null;

                                                                     if(_callThisOnlyOnce == null)
                                                                        _callThisOnlyOnce = CallThisOnlyOnceAsync();

                                                                     return _callThisOnlyOnce;
                                                                   }
     _callThisOnlyOnce = CallThisOnlyOnceAsync();

  return _callThisOnlyOnce;
}

これで、2 つの通話が同時に実行されます。

複数のウェイターに関しては、はい、これを行うことができます。MS のサンプル コードをどこかで見たことがあると思います。たとえば、結果がTask.FromResult(0)静的メンバーに格納され、関数がゼロを返したいときにいつでも返される最適化を示しています。

ただし、このコード サンプルを見つけることができませんでした。

于 2012-11-23T13:39:46.760 に答える