当前位置: 首页 > 知识库问答 >
问题:

nunit如何成功地等待异步空方法完成?

刘曾琪
2023-03-14

在C#中使用async/await时,一般的规则是避免async void,因为这几乎是一个错误,如果方法没有发送返回值,则应该使用task。有道理。奇怪的是,在本周早些时候,我为我编写的一些异步方法编写了一些单元测试,并注意到NUnit建议将异步测试标记为void或返回task。然后我试了一下,果然奏效了。这似乎真的很奇怪,因为nunit框架如何能够运行该方法并等待所有异步操作完成?如果它返回Task,它可以等待任务,然后做它需要做的事情,但是如果它返回void,它如何完成它呢?

所以我破解了源代码并找到了它。我可以在一个小样本中复制它,但我根本无法理解他们在做什么。我想我对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);
        }
    }
}

在运行上面的代码时,您会注意到Done sleep和Done awaiting消息出现在按任意键继续消息之前,这意味着异步方法正在以某种方式等待。

我的问题是,有人能解释一下这里发生了什么吗?synchronizationcontext到底是什么(我知道它用于将工作从一个线程发布到另一个线程),但我仍然不知道如何等待所有工作完成。提前感谢!!

共有1个答案

穆铭晨
2023-03-14

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

AsyncSynchronizationContext类实现自己的消息循环。发送到此上下文的工作将被添加到队列中。当程序调用WaitforPendingOperationStoComplete();时,该方法通过从队列中获取工作并执行它来运行消息循环。如果在console.writeline(“done awaiting”);上设置断点,您将看到它在WaitforPendingOperationStoComplete()方法的主线程上运行。

此外,Async/await功能调用operationstarted()/operationcompleted()方法来通知SynchronizationContext方法何时开始或结束执行。

AsyncSynchronizationContext使用这些通知来统计正在运行但尚未完成的Async方法的数量。当这个计数达到零时,WaitForPendingOperationStoComplete()方法停止运行消息循环,控制流返回到调用方。

若要在调试器中查看此进程,请在同步上下文的postoperationstartedoperationcompleted方法中设置断点。然后逐步执行AsyncMethod调用:

  • 调用AsyncMethod时。NET首先调用OperationStarted()
    • 这将_operationcount设置为1。
      null
      null

 类似资料:
  • 我正在编写一个WinForms应用程序,它将数据传输到USB HID类设备。我的应用程序使用了优秀的通用HID库V6.0,可以在这里找到。简单来说,当我需要向设备写入数据时,这是被调用的代码: 当我的代码退出while循环时,我需要从设备中读取一些数据。但是,设备无法立即响应,因此我需要等待此呼叫返回后再继续。由于当前存在,RequestToGetInputReport()声明如下: GetInp

  • 我通读了Dart/flatter中的Async/Await/then,试图理解为什么aysnc函数中的Await不会等到完成后再继续。在我的UI中,有一个按钮调用一个异步方法来返回一个位置,该位置总是返回null,并且不等待函数完成。 该函数将调用推送到一个新的UI页面,该页面选择一个位置,并应返回一个结果。如何使该函数等待结果?我不是在使用异步吗?

  • 问题内容: 我知道这个问题以前曾被问过,但是所有解决方案都不适合我。 我有一个将参数发送到API的函数,并以列表的形式返回数据。我有一个UITableView设置为使用该列表,但是它在列表分配给变量之前运行。 码: 如果不立即将其作为重复投票,我将不胜感激,这是我尝试的方法。 派遣组 信号量计时 运行变量 其中包括= self和= self 。 编辑:要求提取项目, 问题答案: 您不能-也不应该-

  • 问题内容: 嗨,我的脚本中有2个Ajax调用,我需要它们运行asnyc以节省时间,但是我需要第二个才能等待第一个完成。 有什么想法吗?谢谢 问题答案: 如果使用jQuery 1.5+,则可以使用jQuery 完成。诸如此类的东西(缩短了ajax的简洁性,只需像上面那样传递对象) 您不知道它们将以什么顺序返回,因此,如果您手动滚动此请求,则需要检查另一个请求的状态并等待它返回。

  • 我试图为从服务调用异步函数的函数编写测试,但我一辈子都不知道如何让Jasmine在执行expect函数之前等待异步操作完成。 我试图使用Jasmine的“完成”功能,但我不知道如何实现它。 在本例中,只要 调用时,它立即跳转到expect并失败,因为异步操作尚未完成。

  • 在实际应用中,我对 C# 中的异步和 await 方法进行了说明。请考虑以程,这是工作代码的简单版本: 助手.cs 功能控制器.cs processing.js 现在,问题是第三方服务的所需时间更少(例如:2秒),而所需的时间更多(例如:100秒)。进行两个ajax调用并使用异步和等待是为了呈现自我。屏幕上显示数据1,不要等到自己。Data2已被检索。不幸的是,这种情况没有发生,我必须等待100秒