2012-03-20 12 views
13

W normalnej/synchronicznej/jednowątkowej aplikacji konsoli NDC.Push działa dobrze w celu zarządzania "bieżącym elementem" (potencjalnie na wielu poziomach zagnieżdżania, ale tylko 1 poziom w tym przykładzie).jak zarządzać stosem log4net podobnym do NDC metodami async/await? (stos za zadanie?)

Na przykład:

private static ILog s_logger = LogManager.GetLogger("Program"); 

static void Main(string[] args) 
{ 
    BasicConfigurator.Configure(); 

    DoSomeWork("chunk 1"); 
    DoSomeWork("chunk 2"); 
    DoSomeWork("chunk 3"); 
} 

static void DoSomeWork(string chunkName) 
{ 
    using (NDC.Push(chunkName)) 
    { 
     s_logger.Info("Starting to do work"); 
     Thread.Sleep(5000); 
     s_logger.Info("Finishing work"); 
    } 
} 

Spowoduje to Expect zalogować wyjścia, pokazując „klocek x” wejście NDC tuż na prawo od „Program” (domyślny wzorzec dla podstawowego konfiguratorze)

232 [9] kawałek INFO Program 1 - Rozpoczęcie działają

5279 [9] kawałek INFO Program 1 - wykończeniowe

5279 [9] INFO Program fragment 2 - Rozpoczęcie działają

10292 [9] INFO Program fragment 2 - wykończeniowe

10292 [9] INFO Program fragment 3 - Rozpoczęcie działają

15299 [9] INFO Program chunk 3 - Prace wykończeniowe

Nie mogę jednak dowiedzieć się, jak zachować to przy użyciu "normalnych" metod asynchronicznych.

Na przykład, starając się to zrobić:

private static ILog s_logger = LogManager.GetLogger("Program"); 

static void Main(string[] args) 
{ 
    BasicConfigurator.Configure(); 

    var task1 = DoSomeWork("chunk 1"); 
    var task2 = DoSomeWork("chunk 2"); 
    var task3 = DoSomeWork("chunk 3"); 

    Task.WaitAll(task1, task2, task3); 
} 

static async Task DoSomeWork(string chunkName) 
{ 
    using (log4net.LogicalThreadContext.Stacks["NDC"].Push(chunkName)) 
    //using (log4net.ThreadContext.Stacks["NDC"].Push(chunkName)) 
    { 
     s_logger.Info("Starting to do work"); 
     await Task.Delay(5000); 
     s_logger.Info("Finishing work"); 
    } 
} 

pokazuje im wszystko począwszy od „normalnie”, ale po zakończeniu wykonywania zadania na inny wątek, stos jest tracona (Miałam nadzieję, że się log4net.LogicalThreadContext być może TPL-'aware ").

234 [10] INFO Program fragment 1 - Rozpoczęcie działają

265 [10] INFO Program fragment 2 - Rozpoczęcie działają

265 [10] INFO Program fragment 3 - Uruchamianie do pracy

5280 [7] Informacje o programie (zerowy) - prace wykończeniowe

5280 [12] Informacje o programie (zerowy) - wykończeniowe

5280 [12] Informacje o programie (null) - wykończeniowe

Poza dodanie nowego TaskContext (lub podobne) do log4net, czy istnieje sposób śledzenia tego rodzaju działalności?

Chodzi o to, aby tak się stało z cukrem składającym się z asynów/czekania - albo wymuszenie jakiegoś rodzaju powinowactwa nitki, czy robienie takich rzeczy, jak utrzymywanie współbieżnego słownika po kluczowanym przez zadanie, są prawdopodobnie możliwymi opcjami, ale staram się zachować tak blisko synchronicznej wersji kodu, jak to możliwe. :)

+5

FYI, Niedawno odkryłem, że Microsoft naprawił 'CallContext' w .NET 4.5 RTW, aby pracować z' async'. Tak więc NDC log4net i inne rozwiązania używające 'Logical * Data' będą działały zgodnie z oczekiwaniami w przypadku metod' async' (tylko w .NET 4.5). –

+0

@StephenCleary super! Dzięki! –

+0

Nie jestem pewien, czy to problem pochodzący z kontekstu wątku logicznego. Implementacja log4net wygląda na niepoprawną, ponieważ wątek nadrzędny i podrzędny mają ten sam stos. Dziecko powinno otrzymać klona stosu nadrzędnego, więc jeśli rodzic modyfikuje stos, nie zaburza to ... –

Odpowiedz

15

W tej chwili nie ma dobrej historii dla async kontekstów połączeń logicznych. Nie można w tym celu użyć

CallContext.Logiczne CallContext nie rozumie, w jaki sposób metody powracają wcześniej i można je później wznowić, więc nie zawsze będzie działać poprawnie dla kodu używającego prostego paralelizmu, takiego jak Task.WhenAll.

Aktualizacja:CallContext została zaktualizowana w .NET 4.5 RTW aby prawidłowo współpracować z async metod.

Sprawdziłem log4net; LogicalThreadContext jest udokumentowane jako używające CallContext, ale wystąpił błąd, który powodował, że korzystał z nielogicznych kontekstów (ustalonych w ich SVN w dniu 2 lutego 2012 r., Obecne wydanie 1.2.11 nie zawiera tej poprawki). Nawet jeśli zostanie naprawiony, nadal nie będzie działał z async (ponieważ logiczne CallContext nie działa z async).

Kiedy potrzebuję kontekstu logicznego wywołania async, tworzę klasę zawierającą dane kontekstu i zachowuję wszystkie moje metody async w stylu funkcjonalnym jako elementy tej klasy. To jest z pewnością nie jest idealnym rozwiązaniem, ale jest to brudny hack, który działa.

W międzyczasie należy postępować zgodnie z wersją suggestion that Microsoft provide some mechanism for this.

P.S. Słownik współdziałający z kluczem Task nie będzie działał, ponieważ metody async niekoniecznie są uruchomione (tj. W twoim przykładowym kodzie, przy using oświadczeniu, Task.CurrentId byłoby null, ponieważ nie ma żadnego zadania faktycznie wykonywanego w tym momencie).

Także powinowactwo nitek ma swoje własne problemy. W rzeczywistości potrzebujesz oddzielnego wątku dla każdej niezależnej operacji asynchronicznej. Pa, pa, skalowalność ...

+0

Niestety nie było to jasne - w przypadku podejścia do współbieżnego słownika, nie korzystałem z metod asynchronicznych w ogóle, ale zamiast tego będzie "jawnie" przy użyciu zadań (czyli co już można zrobić w VS2010/.NET4). Żałuję, że nie mogę po prostu dołączyć danych do zadania (cóż, bez tworzenia podklasy i tworzenia własnych) i uzyskać "kontekst bieżącego zadania" podobny do HttpContext.Current, chociaż tak naprawdę nie myślałem, że to. :) –

+0

Rozumiem. Cóż, jeśli chcesz dołączyć dane do 'Task' (innego niż' AsyncState'), możesz użyć [Connected Properties] (http://connectedproperties.codeplex.com/), ale IMO jest równie łatwy w użyciu ' ConcurrentDictionary' dla tego scenariusza. –

+0

@ StephenCleary Stephen Toub odpowiedział na twój powiązany wpis na User Voice, mówiąc, że twoje początkowe informacje były nieaktualne, a CallContext jest odpowiedni do asynchronizacji/czekania. – Lex