Jak nunit pomyślnie czeka na zakończenie metody async void?
Podczas używania async/await
W C#, ogólną zasadą jest unikanie async void
, ponieważ jest to prawie ogień i zapominanie, a raczej Task
powinno być używane, jeśli nie jest wysyłana żadna wartość zwracana z metody. To ma sens. Dziwne jest jednak to, że na początku tygodnia pisałem testy jednostkowe dla kilku metod async
, które napisałem, i zauważyłem, że NUnit zasugerował, aby oznaczyć async
testy jako void
lub zwracając Task
. Potem spróbowałem i na pewno zadziałało. To wydawało się naprawdę dziwne, jak nunit framework czy można uruchomić metodę i czekać na zakończenie wszystkich operacji asynchronicznych? Jeśli zwróci zadanie, może po prostu poczekać na zadanie, a następnie zrobić to, co musi zrobić, ale jak może to zrobić, jeśli zwróci void?
Więc złamałem kod źródłowy i znalazłem go. Mogę to odtworzyć w małej próbce, ale po prostu nie mogę zrozumieć, co robią. Chyba za mało wiem o SynchronizationContext i jak to działa. Oto kod:
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);
}
}
}
Kiedy uruchamiając powyższy kod, zauważysz, że wiadomości Done Sleeping i Done waiting pojawiają się przed naciśnij dowolny klawisz, aby kontynuować wiadomość, co oznacza, że metoda asynchroniczna jest w jakiś sposób czekana.
Moje pytanie brzmi, czy ktoś może wyjaśnić, co się tu dzieje? Czym dokładnie jestSynchronizationContext
(wiem, że służy do publikowania prac z jednego wątku do drugiego), ale nadal jestem zdezorientowany, jak możemy czekać na całą pracę do wykonania. Z góry dzięki!! 1 answers
A SynchronizationContext
pozwala na wysyłanie prac do kolejki, która jest przetwarzana przez inny wątek (lub przez pulę wątków) - zwykle używana jest do tego pętla komunikatów frameworka interfejsu użytkownika.
Na async
/await
funkcja wewnętrznie używa bieżącego kontekstu synchronizacji, aby powrócić do właściwego wątku po zakończeniu zadania, na które czekałeś.
Klasa AsyncSynchronizationContext
implementuje własną pętlę komunikatów. Praca, która zostanie opublikowana w tym kontekście, zostanie dodana do kolejki.
Gdy twój program wywoła WaitForPendingOperationsToComplete();
, ta metoda uruchamia się pętli komunikatów, pobierając pracę z kolejki i wykonując ją.
Jeśli ustawisz punkt przerwania na Console.WriteLine("Done awaiting");
, zobaczysz, że działa on w głównym wątku w metodzie WaitForPendingOperationsToComplete()
.
Dodatkowo async
/await
funkcja wywołuje OperationStarted()
/ OperationCompleted()
metody powiadamiające SynchronizationContext
o każdym uruchomieniu lub zakończeniu wykonywania metody async void
.
The AsyncSynchronizationContext
używa tych powiadomień, aby zapisać liczbę metod async
, które są uruchomione i nie zostały jeszcze zakończone. Gdy liczba ta osiągnie zero, metoda WaitForPendingOperationsToComplete()
przestaje uruchamiać pętlę wiadomości, a przepływ sterowania powraca do wywołującego.
Aby wyświetlić ten proces w debuggerze, Ustaw punkty przerwania w Post
, OperationStarted
i OperationCompleted
metody kontekstu synchronizacji. Następnie przejdź przez wywołanie AsyncMethod
:
- Po wywołaniu. NET pierwsze wywołanie
- to ustawia {[22] } na 1.
- wtedy ciało
AsyncMethod
zaczyna działać (i uruchamia zadanie w tle) - W
await
w przypadku, gdy zadanie nie jest jeszcze ukończone]} -
currentContext.WaitForPendingOperationsToComplete();
gets called - żadne operacje nie są jeszcze dostępne w kolejce, więc główny wątek przechodzi w tryb uśpienia w
_operationsAvailable.WaitOne();
- W Tle wątku:
- w pewnym momencie zadanie kończy się spaniem
- wyjście:
Done sleeping!
- delegat kończy wykonanie, a zadanie zostaje oznaczone jako ukończone
- metoda
Post()
zostaje wywołana, pytając o kontynuację, która reprezentuje resztę zAsyncMethod
- główny wątek budzi się, ponieważ kolejka nie jest już pusta
- pętla komunikatów uruchamia kontynuację, tym samym wznawia wykonywanie
AsyncMethod
- wyjście:
Done awaiting
-
[19]} kończy wykonanie, powodując wywołanie. NET]}
-
_operationCount
jest zmniejszona do 0, co oznacza pętlę wiadomości jako kompletną
-
- Kontrola powraca do pętli komunikatów
- pętla wiadomości kończy się, ponieważ została oznaczona jako w tym celu należy skontaktować się z Działem obsługi klienta.]}
- wyjście:
Press any key to continue. . .
OperationStarted()
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-02-22 20:19:26