2015-05-05 9 views
7

Właśnie przetestowałem coś, co na pewno by się nie udało, ale ku mojemu zdziwieniu zadziałało bezbłędnie i udowadniało, że nadal jestem pod wrażeniem tego, jak działa async-await.Pętla ciała wątku Async, po prostu działa, ale jak?

Utworzono wątek, przekazując delegata async void jako treść wątku. Oto uproszczeniem mojego kodu:

var thread = new Thread(async() => { 
    while(true) { 
     await SomeLengthyTask(); 
     ... 
    } 
}); 
thread.Start(); 
thread.Join(); 

Chodzi o to, że o ile mi zrozumieć, gdy wykonanie uderza słowa kluczowego await istnieje niejawna powrót ze sposobu, w tym przypadku ciało pętli wątku, podczas gdy reszta kodu jest zawijana w kontynuację wywołania zwrotnego.

Z tego powodu byłem pewien, że wątek zakończy się, gdy tylko await wykona egzekucję, ale tak nie jest!

Czy ktoś wie, jak ta magia jest rzeczywiście realizowana? Czy funkcja async jest rozebrana i czy async czeka w sposób synchroniczny, czy też CLR wykonał jakąś czarną magię, która umożliwia jej wznowienie wątku, który powstał z powodu await?

Odpowiedz

7

Wątek rzeczywiście kończy się bardzo szybko.

Ale ponieważ konstruktor Thread nie akceptuje lambda async, otrzymujesz delegata async void.

Oryginalny wątek zakończy się, a kontynuacja (reszta po await) zostanie opublikowana na ThreadPool i ostatecznie uruchomiona na innym wątku.

Można sprawdzić, sprawdzając identyfikator wątku:

var thread = new Thread(async() => 
{ 
    while (true) 
    { 
     Console.WriteLine(Thread.CurrentThread.ManagedThreadId); 
     await SomeLengthyTask(); 
     Console.WriteLine(Thread.CurrentThread.ManagedThreadId); 
    } 
}); 
thread.Start(); 
thread.Join(); 

Console.ReadLine(); 

wyjściowa:

3 
5 
5 
... 
5 

Aby przykład prostszy załóżmy masz ten Run metody:

void Run(Action action) 
{ 
    action(); 
} 

Zadzwonisz do niego ze swoim delegatem async

Run(async() => 
{ 
    while(true) 
    { 
     await SomeLengthyTask(); 
     ... 
    } 
}); 

Realizacja Run zakończy się prawie natychmiast po osiągnięciu pierwszego await i powraca. Reszta delegata async będzie kontynuowana na ThreadPool z innym wątkiem.


Generalnie, za każdym razem osiągnąć await w zrealizowaniu async metody wątek jest stracony i kontynuacji (reszta po zakończeniu oczekiwane zadania) zostanie wysłana do puli wątków (chyba, że ​​jeśli istnieje SynchronizationContext obecny, jak w wątku UI). Być może wykonanie będzie na tym samym wątku (jak w moim przykładzie z 5), ale może również nie.

W twoim przypadku wątek utworzony jawnie nie jest częścią ThreadPool, więc zostanie definitywnie zakończony, a reszta zostanie uruchomiona na innym wątku.

+0

Dzięki za poprawne wyjaśnienie między lambdą a delegatem, odpowiednio zredagowałem moje pytanie. Tak więc, aby zrozumieć, co właściwie robi CLR, po prostu uniemożliwia się Join() oryginalnego wątku? – BlueStrat

+2

@ BlueStrat Nie, to ma miejsce. Właśnie dlatego dodałem 'Console.ReadLine' w moim przykładzie. Bez niego moja aplikacja konsolowa kończy się przed dotarciem do interesującej części. – i3arnon

+1

@ BlueStrat Nic tu nie robi nic specjalnego. Metody 'async' po prostu nie działają w tym samym wątku. Mogą to zrobić, ale tak długo, jak nie istnieje Kontekst Synchronizacji, uruchamiają każdą synchroniczną część w ThreadPool – i3arnon

Powiązane problemy