在C#中使用async/await
时,一般的规则是避免async void
,因为这几乎是一场火并忘记了,而如果没有从该方法发送返回值,应该使用Task
。说得通。但奇怪的是,本周早些时候,我正在为我编写的几个async
方法编写一些单元测试,并注意到NUnit建议将async
测试标记为void
或返回Task
。然后我试了一下,果然,它工作。这看起来很奇怪,因为nunit框架如何能够运行该方法并等待所有异步操作完成?如果它返回任务,它可以等待任务,然后做它需要做的事情,但如果它返回无效,它怎么能将它关闭?nunit如何成功等待异步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);
}
}
}
当运行上面的代码,你会发现,睡够了,并完成等待消息显示按任意键之前继续的消息,这意味着异步方法以某种方式被侍候。
我的问题是,有人可以解释这里发生了什么? SynchronizationContext
究竟是什么(我知道它用于将工作从一个线程发布到另一个线程),但我仍然对如何等待所有工作完成感到困惑。提前致谢!!
稍作修改:'OperationStarted' /'OperationCompleted'只呼吁'异步void'方法,而不是'异步任务'方法。此外,'SynchronizationContext'具有事件循环以外的其他用途 - 特别是'AspNetSynchronizationContext'不是基于消息循环。我有[MSDN文章](http://msdn.microsoft.com/en-us/magazine/gg598924.aspx)更详细。 – 2013-02-22 20:13:05