BFr*_*ree 33 .net c# nunit async-await
async/await
在C#中使用时,通常的规则是避免,async void
因为这几乎是火,忘记了,Task
如果没有从方法发送返回值,则应该使用.说得通.但奇怪的是,在本周早些时候,我正在为async
我编写的一些方法编写一些单元测试,并注意到NUnit建议将async
测试标记为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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
运行上面的代码时,您会注意到在按任意键继续消息之前显示完成睡眠和完成等待消息,这意味着异步方法以某种方式等待.
我的问题是,有人可以解释这里发生了什么吗?究竟是什么SynchronizationContext
(我知道它用于将工作从一个线程发布到另一个线程)但我仍然对如何等待所有工作完成感到困惑.提前致谢!!
Dan*_*iel 32
A SynchronizationContext
允许将工作发布到由另一个线程(或线程池)处理的队列 - 通常UI框架的消息循环用于此.该async
/ await
功能在内部使用当前同步上下文你在等完成任务后返回到正确的线程.
在AsyncSynchronizationContext
类实现自己的消息循环.发布到此上下文的工作将添加到队列中.程序调用时WaitForPendingOperationsToComplete();
,该方法通过从队列中获取工作并执行它来运行消息循环.如果设置断点Console.WriteLine("Done awaiting");
,您将看到它在WaitForPendingOperationsToComplete()
方法中的主线程上运行.
此外,async
/ await
feature调用OperationStarted()
/ OperationCompleted()
methods以SynchronizationContext
在async 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()
_operationCount
递减为0,这将消息循环标记为完成WaitForPendingOperationsToComplete
返回给调用者Press any key to continue. . .