35

async/awaitC# で使用する場合、メソッドから戻り値が送信されない場合は、使用async voidする必要があります。Task理にかなっています。奇妙なのは、週の初めに私が書いたいくつかのメソッドの単体テストを書いていて、NUnit がテストをいずれかまたは戻り値としてasyncマークすることを提案していることに気づいたことです。私はそれを試してみましたが、確かにうまくいきました。nunit フレームワークはどのようにしてメソッドを実行し、すべての非同期操作が完了するのを待つことができるのでしょうか? Task が返された場合は、そのタスクを待ってから必要な処理を実行できますが、void が返された場合はどうすればそれを実行できるでしょうか?asyncvoidTask

それで、ソースコードをクラックして開いて見つけました。小さなサンプルでそれを再現することはできますが、彼らが何をしているのかまったく理解できません。SynchronizationContext とそれがどのように機能するかについて、私は十分に知らないと思います。コードは次のとおりです。

class Program
{
    static void Main(string[] args)
    {
        RunVoidAsyncAndWait();

        Console.WriteLine("Press any key to continue. . .");
        Console.ReadKey(true);
    }

    private static void RunVoidAsyncAndWait()
    {
        var previousContext = SynchronizationContext.Current;
        var currentContext = new AsyncSynchronizationContext();
        SynchronizationContext.SetSynchronizationContext(currentContext);

        try
        {
            var myClass = new MyClass();
            var method = myClass.GetType().GetMethod("AsyncMethod");
            var result = method.Invoke(myClass, null);
            currentContext.WaitForPendingOperationsToComplete();
        }
        finally
        {
            SynchronizationContext.SetSynchronizationContext(previousContext);
        }
    }
}

public class MyClass
{
    public async void AsyncMethod()
    {
        var t = Task.Factory.StartNew(() =>
        {
            Thread.Sleep(1000);
            Console.WriteLine("Done sleeping!");
        });

        await t;
        Console.WriteLine("Done awaiting");
    }
}

public class AsyncSynchronizationContext : SynchronizationContext
{
    private int _operationCount;
    private readonly AsyncOperationQueue _operations = new AsyncOperationQueue();

    public override void Post(SendOrPostCallback d, object state)
    {
        _operations.Enqueue(new AsyncOperation(d, state));
    }

    public override void OperationStarted()
    {
        Interlocked.Increment(ref _operationCount);
        base.OperationStarted();
    }

    public override void OperationCompleted()
    {
        if (Interlocked.Decrement(ref _operationCount) == 0)
            _operations.MarkAsComplete();

        base.OperationCompleted();
    }

    public void WaitForPendingOperationsToComplete()
    {
        _operations.InvokeAll();
    }

    private class AsyncOperationQueue
    {
        private bool _run = true;
        private readonly Queue _operations = Queue.Synchronized(new Queue());
        private readonly AutoResetEvent _operationsAvailable = new AutoResetEvent(false);

        public void Enqueue(AsyncOperation asyncOperation)
        {
            _operations.Enqueue(asyncOperation);
            _operationsAvailable.Set();
        }

        public void MarkAsComplete()
        {
            _run = false;
            _operationsAvailable.Set();
        }

        public void InvokeAll()
        {
            while (_run)
            {
                InvokePendingOperations();
                _operationsAvailable.WaitOne();
            }

            InvokePendingOperations();
        }

        private void InvokePendingOperations()
        {
            while (_operations.Count > 0)
            {
                AsyncOperation operation = (AsyncOperation)_operations.Dequeue();
                operation.Invoke();
            }
        }
    }

    private class AsyncOperation
    {
        private readonly SendOrPostCallback _action;
        private readonly object _state;

        public AsyncOperation(SendOrPostCallback action, object state)
        {
            _action = action;
            _state = state;
        }

        public void Invoke()
        {
            _action(_state);
        }
    }
}

上記のコードを実行すると、「Press any key to continue」メッセージの前に「Done Sleeping」と「Done awaiting」メッセージが表示されることに気付くでしょう。これは、非同期メソッドが何らかの形で待機していることを意味します。

私の質問は、誰かがここで何が起こっているのかを説明してもらえますか? 正確には何ですかSynchronizationContext(あるスレッドから別のスレッドに作業を投稿するために使用されていることは知っています)が、すべての作業が完了するのを待つ方法についてはまだ混乱しています. 前もって感謝します!!

4

1 に答える 1

35

SynchronizationContextは、別のスレッド (またはスレッド プール) によって処理されるキューに作業をポストすることを許可します。通常、これには UI フレームワークのメッセージ ループが使用されます。async/機能はawait、待機中のタスクが完了した後、現在の同期コンテキストを内部的に使用して正しいスレッドに戻ります。

このAsyncSynchronizationContextクラスは、独自のメッセージ ループを実装します。このコンテキストに投稿された作業は、キューに追加されます。プログラムが を呼び出すとWaitForPendingOperationsToComplete();、そのメソッドはキューから作業を取得して実行することでメッセージ ループを実行します。にブレークポイントを設定すると、メソッドConsole.WriteLine("Done awaiting");内のメイン スレッドで実行されることがわかりますWaitForPendingOperationsToComplete()

さらに、async/await機能はOperationStarted()/メソッドを呼び出して、メソッドの実行が開始または終了するたびOperationCompleted()に に通知します。SynchronizationContextasync void

はこれらの通知を使用して、実行中でまだ完了していないメソッドAsyncSynchronizationContextの数を数えます。asyncこのカウントがゼロになると、WaitForPendingOperationsToComplete()メソッドはメッセージ ループの実行を停止し、制御フローは呼び出し元に戻ります。

このプロセスをデバッガーで表示するには、同期コンテキストの および メソッドにPostブレークOperationStartedポイントを設定します。OperationCompleted次に、AsyncMethod呼び出しをステップ実行します。

  • AsyncMethod呼び出されると、.NET が最初に呼び出しますOperationStarted()
    • これにより、_operationCountが 1 に設定されます。
  • 次に、本体のAsyncMethod実行が開始されます (そして、バックグラウンド タスクが開始されます)。
  • awaitステートメントで、AsyncMethodタスクがまだ完了していないため、制御を譲ります
  • currentContext.WaitForPendingOperationsToComplete();呼ばれる
  • キューにはまだ使用可能な操作がないため、メイン スレッドは次の時刻にスリープ状態になります。_operationsAvailable.WaitOne();
  • バックグラウンド スレッド:
    • ある時点で、タスクはスリープを終了します
    • 出力:Done sleeping!
    • デリゲートは実行を終了し、タスクは完了としてマークされます
    • メソッドが呼び出され、Post()残りの部分を表す継続がキューに入れられます。AsyncMethod
  • キューが空でなくなったため、メインスレッドが起動します
  • メッセージループは継続を実行し、したがって実行を再開しますAsyncMethod
  • 出力:Done awaiting
  • AsyncMethod実行を終了し、.NET を呼び出しますOperationComplete()
    • _operationCount0 にデクリメントされ、メッセージ ループが完了したことを示します。
  • 制御はメッセージ ループに戻ります
  • メッセージ ループは完了としてマークされたため終了WaitForPendingOperationsToCompleteし、呼び出し元に戻ります。
  • 出力:Press any key to continue. . .
于 2013-02-22T19:56:48.123 に答える