22

複数のスレッドで同時に実行できないメソッドがあります (ファイルに書き込みます)。lockメソッドが であるため、使用できませんasync。別のスレッドでメソッドを呼び出さないようにするには? 2 回目に呼び出す代わりに、プログラムは前の呼び出しが完了するまで待機する必要があります (これも非同期で)。

たとえば、次のコードを使用して新しい C# コンソール アプリケーションを作成します。

using System;
using System.IO;
using System.Threading.Tasks;
namespace ConsoleApplication1 {
    internal class Program {

        private static void Main () {
            CallSlowStuff();
            CallSlowStuff();
            Console.ReadKey();
        }

        private static async void CallSlowStuff () {
            try {
                await DoSlowStuff();
                Console.WriteLine("Done!");
            }
            catch (Exception e) {
                Console.WriteLine(e.Message);
            }
        }

        private static async Task DoSlowStuff () {
            using (File.Open("_", FileMode.Create, FileAccess.Write, FileShare.None)) {
                for (int i = 0; i < 10; i++) {
                    await Task.Factory.StartNew(Console.WriteLine, i);
                    await Task.Delay(100);
                }
            }
        }
    }
}

この例では、2 番目の呼び出しCallSlowStuffで例外がスローされます。これは、既に開かれているファイルにアクセスできないためです。混合しないため、追加lockはオプションではlockありません。async(Mainメソッドは変更不可と見なされるべきです。実際のアプリケーションでCallSlowStuffは、どこでも呼び出すことができるインターフェイス メソッドです。)

質問:CallSlowStuffメイン スレッドをブロックせずに、現在実行中の呼び出しが完了するまで待機する後続の呼び出しを行うにはどうすればよいですか? async/await とタスク (およびおそらく Rx) だけを使用して実行できますか?

4

3 に答える 3

32

ある種の非同期ロックが必要です。Stephen Toub は、同期プリミティブ( を含む)の構築に関する一連の記事をasyncAsyncLockまとめています。のバージョンは、Stephen Cleary のAsyncEx ライブラリAsyncLockにも含まれています。

SemaphoreSlimしかし、おそらくより簡単な解決策は、非同期待機をサポートする組み込みの を使用することです。

private static SemaphoreSlim SlowStuffSemaphore = new SemaphoreSlim(1, 1);

private static async void CallSlowStuff () {
    await SlowStuffSemaphore.WaitAsync();
    try {
        await DoSlowStuff();
        Console.WriteLine("Done!");
    }
    catch (Exception e) {
        Console.WriteLine(e.Message);
    }
    finally {
        SlowStuffSemaphore.Release();
    }
}
于 2013-01-21T01:09:57.233 に答える
1

CallSlowStuffメッセージをTPL DataFlow ActionBlockにポストするように のメソッド本体を変更し、それを 1 段階の並列処理に構成することを検討します。

したがって、単一の ActionBlock をどこかに保管してください。

ActionBlock actionBlock = 
    new ActionBlock<object>(
         (Func<object,Task>)CallActualSlowStuff,
         new ExecutionDataflowBlockOptions(){MaxDegreeOfParallelism=1});

今:

public void CallSlowStuff()
{
    actionBlock.Post(null);
}

private async Task CallActualSlowStuff(object _)
{
    using (File.Open("_", FileMode.Create, FileAccess.Write, FileShare.None)) {
        for (int i = 0; i < 10; i++) {
            await Task.Factory.StartNew(Console.WriteLine, i);
            await Task.Delay(100);
        }
    }
}
于 2013-01-21T00:44:36.677 に答える
0

このインスタンスではセマフォを使用できます。http://msdn.microsoft.com/en-us/library/system.threading.semaphore.aspx を参照してください。

何かのようなもの:

private Semaphore sem = new Semaphore(1, 1);

private static async Task DoSlowStuff () {
  sem.WaitOne();
  // your stuff here
  sem.Release();
}
于 2013-01-21T01:01:41.697 に答える