如何NUnit的成功等待异步void的方法来完成? [英] How does nunit successfully wait for async void methods to complete?

查看:201
本文介绍了如何NUnit的成功等待异步void的方法来完成?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这可能是一个有点宽泛的问题,但我会想试试。

This may be a bit of a broad question, but I'll try anyway.

所以,一般的规则是避免异步无效因为这是pretty太大的发射后不管,而是一个工作应使用,如果没有返回值从运输方式。说得通。有什么奇怪的,虽然是在本周早些时候,我在写一些单元测试了几个异步方法我写的,并注意到NUnit的建议,以纪念异步测试,无论是无效或返回工作。然后,我试了一下,果然,它的工作。这似乎很奇怪的,因为NUnit的框架如何将能够运行的方法,等待所有异步操作来完成?如果返回的任务,它可以只等待任务,然后做它需要做的,但它怎么可以把它关闭,如果它返回void?

So when using async/await in C#, the general rule is to avoid async void as that's pretty much a fire and forget, rather a Task should be used if no return value is sent from the method. Makes sense. What's strange though is that earlier in the week I was writing some unit tests for a few async methods I wrote, and noticed that NUnit suggested to mark the async tests as either void or returning Task. I then tried it, and sure enough, it worked. This seemed really strange, as how would the nunit framework be able to run the method and wait for all asynchronous operations to complete? If it returns Task, it can just await the task, and then do what it needs to do, but how can it pull it off if it returns void?

所以我破解打开源$ C ​​$ c和发现它。我可以重现它的小样本,但我根本无法使他们在做什么感觉。我想我不充分了解的SynchronizationContext以及如何工作的。这里的code:

So I cracked open the source code and found it. I can reproduce it in a small sample, but I simply cannot make sense of what they're doing. I guess I don't know enough about the SynchronizationContext and how that works. Here's the code:

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);
        }
    }
}

在运行上面的code,你会发现,在完成睡眠和完成等待消息显示的的的preSS任意键继续的消息,这意味着异步方法不知何故正在等待的。

When running the above code, you'll notice that the Done Sleeping and Done awaiting messages show up before the Press any key to continue message, which means the async method is somehow being waited on.

我的问题是,能有人照顾解释这里发生了什么?到底是什么的SynchronizationContext (我知道这是用于发布从一个线程到另一个工作),但我仍然感到困惑,我们如何可以等待所有的工作要完成。在此先感谢!

My question is, can someone care to explain what's happening here? What exactly is the SynchronizationContext (I know it's used to post work from one thread to another) but I'm still confused as to how we can wait for all the work to be done. Thanks in advance!!

推荐答案

A 的SynchronizationContext 允许发布工作,由另一个线程处理(或线程池队列) - 通常是UI框架的消息循环用于此。
异步 / 等待功能在内部使用当前同步情况下,你都在等待任务后返回到正确的线程已完成。

A SynchronizationContext allows posting work to a queue that is processed by another thread (or by a thread pool) -- usually the message loop of the UI framework is used for this. The async/await feature internally uses the current synchronization context to return to the correct thread after the task you were waiting for has completed.

AsyncSynchronizationContext 类实现自己的消息循环。被发布到这个环境中工作被添加到队列中。
当你的程序调用 WaitForPendingOperationsToComplete(); ,该方法通过运行从队列抓工作,并执行它的消息循环。
如果设置断点Console.WriteLine(完成等待); ,你会看到,它运行在主线程上的 WaitForPendingOperationsToComplete( )方法。

The AsyncSynchronizationContext class implements its own message loop. Work that is posted to this context gets added to a queue. When your program calls WaitForPendingOperationsToComplete();, that method runs a message loop by grabbing work from the queue and executing it. If you set a breakpoint on Console.WriteLine("Done awaiting");, you will see that it runs on the main thread within the WaitForPendingOperationsToComplete() method.

此外,在异步 / 等待功能调用 OperationStarted() / OperationCompleted()方法来通知的SynchronizationContext 每当异步无效方法启动或完成执行。

Additionally, the async/await feature calls the OperationStarted() / OperationCompleted() methods to notify the SynchronizationContext whenever an async void method starts or finishes executing.

AsyncSynchronizationContext 使用这些通知让数的计数异步运行和避风港'的方法ŧ尚未完成。当该计数达到零,则 WaitForPendingOperationsToComplete()方法停止运行消息循环,并且控制流程返回到调用者。

The AsyncSynchronizationContext uses these notifications to keep a count of the number of async methods that are running and haven't completed yet. When this count reaches zero, the WaitForPendingOperationsToComplete() method stops running the message loop, and the control flow returns to the caller.

要在调试器中查看此过程中,设置断点在发表 OperationStarted OperationCompleted 同步上下文的方法。然后通过 AsyncMethod 呼叫步骤:

To view this process in the debugger, set breakpoints in the Post, OperationStarted and OperationCompleted methods of the synchronization context. Then step through the AsyncMethod call:


  • AsyncMethod 被调用时,.NET首先调用 OperationStarted()

    • 设置了 _operationCount 1

    • When AsyncMethod is called, .NET first calls OperationStarted()
      • This sets the _operationCount to 1.

      • 在某些时候任务完成睡

      • 输出:<!code>完成睡

      • 委托执行完毕,任务被标记为完成

      • 发布()方法被调用,入队延续了重新presents的其余部分的 AsyncMethod

      • at some point the task finishes sleeping
      • Output: Done sleeping!
      • the delegate finishes execution and the task gets marked as complete
      • the Post() method gets called, enqueuing a continuation that represents the remainder of the AsyncMethod

      • _operationCount 递减到0,这标志着消息循环为完成

      • the _operationCount is decremented to 0, which marks the message loop as complete

      这篇关于如何NUnit的成功等待异步void的方法来完成?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆