在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