2013-03-14 15 views
37

Mam następujący setup przykładowy kod w aplikacji WebAPI:Czy można uzyskać dobry ślad stosu w metodach asynchronicznych .NET?

[HttpGet] 
public double GetValueAction() 
{ 
    return this.GetValue().Result; 
} 

public async Task<double> GetValue() 
{ 
    return await this.GetValue2().ConfigureAwait(false); 
} 

public async Task<double> GetValue2() 
{ 
    throw new InvalidOperationException("Couldn't get value!"); 
} 

Niestety, kiedy GetValueAction dostanie, ślad stosu, który wraca znaczy:

" at MyProject.Controllers.ValuesController.<GetValue2>d__3.MoveNext() in c:\dev\MyProject\MyProject\Controllers\ValuesController.cs:line 61 --- End of stack trace from previous location where exception was thrown --- 
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) 
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) 
at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1.ConfiguredTaskAwaiter.GetResult() 
at MyProject.Controllers.ValuesController.<GetValue>d__0.MoveNext() in c:\dev\MyProject\MyProject\Controllers\ValuesController.cs:line 56" 

Tak więc, mam (zniekształcone) GetValue2 i GetValue w śledzeniu, ale bez wzmianki o GetValueAction. czy robię coś źle? Czy istnieje inny wzór, który pozwoli mi uzyskać bardziej kompletne ślady stosu?

EDYCJA: moim celem nie jest pisanie kodu polegającego na śledzeniu stosu, ale raczej ułatwienie debugowania metod asynchronicznych.

+3

Możesz zajrzeć do [tego artykułu] (http://msdn.microsoft.com/en-us/magazine/jj891052.aspx) na temat asynchronicznych łańcuchów przyczyn. –

+0

@ChaseMedallion: Zobacz moją zaktualizowaną odpowiedź. –

+1

Nie bezpośrednio odpowiedź, ale [ta odpowiedź] (http://stackoverflow.com/a/28633192/868227) daje wskazówkę, jak odszyfrować oryginalną metodę podaną ramkę stosu asynchronicznego. –

Odpowiedz

33

Po pierwsze, ślady stosu nie robią tego, co większość ludzi uważa. Mogą być przydatne podczas debugowania, ale nie są przeznaczone do użycia w środowisku wykonawczym, szczególnie w ASP.NET.

Również ślad stosu jest technicznie o gdzie kod wraca do nie gdzie kod pochodzi. Z prostym (synchronicznym) kodem, oba są takie same: kod zawsze powraca do jakiejkolwiek metody, która go nazywa. Jednak w przypadku kodu asynchronicznego te dwa są różne. Znowu ślad stosu mówi, co się stanie, następnie, ale interesuje Cię, co się stało w przeszłości.

Ramka stosu nie jest poprawną odpowiedzią na twoje potrzeby. Eric Lippert explains this well in his answer here.

The MSDN article że @ColeCampbell związanej opisuje jeden sposób, aby śledzić „łańcuchy casuality” (gdzie kod przyszedł z) z kodem async. Niestety, takie podejście jest ograniczone (np. Nie obsługuje scenariuszy fork/join); jest to jednak jedyne znane mi podejście, które działa w aplikacjach Windows Store.

Od kiedy korzystasz z platformy ASP.NET z pełnym środowiskiem wykonawczym .NET 4.5, masz dostęp do wydajniejszego rozwiązania do śledzenia łańcuchów sukcesów: kontekstu logicznych połączeń. Twoje metody async muszą jednak "włączyć", więc nie dostaniesz za darmo, jak za pomocą śledzenia stosu. Właśnie napisałem to w blogu, który nie został jeszcze opublikowany, więc dostajesz podgląd. :)

można zbudować "stos" połączeń samodzielnie wokół logicznym kontekście połączenia takie jak:

public static class MyStack 
{ 
    // (Part A) Provide strongly-typed access to the current stack 
    private static readonly string slotName = Guid.NewGuid().ToString("N"); 
    private static ImmutableStack<string> CurrentStack 
    { 
    get 
    { 
     var ret = CallContext.LogicalGetData(name) as ImmutableStack<string>; 
     return ret ?? ImmutableStack.Create<string>(); 
    } 
    set { CallContext.LogicalSetData(name, value); } 
    } 

    // (Part B) Provide an API appropriate for pushing and popping the stack 
    public static IDisposable Push([CallerMemberName] string context = "") 
    { 
    CurrentStack = CurrentStack.Push(context); 
    return new PopWhenDisposed(); 
    } 
    private static void Pop() { CurrentContext = CurrentContext.Pop(); } 
    private sealed class PopWhenDisposed : IDisposable 
    { 
    private bool disposed; 
    public void Dispose() 
    { 
     if (disposed) return; 
     Pop(); 
     disposed = true; 
    } 
    } 

    // (Part C) Provide an API to read the current stack. 
    public static string CurrentStackString 
    { 
    get { return string.Join(" ", CurrentStack.Reverse()); } 
    } 
} 

(ImmutableStack jest dostępny here). Następnie można go używać tak:

static async Task SomeWork() 
{ 
    using (MyStack.Push()) 
    { 
    ... 
    Console.WriteLine(MyStack.CurrentStackAsString + ": Hi!"); 
    } 
} 

Zaletą tego podejścia jest to, że działa z wszystkieasync kod: fork/join, niestandardowe awaitables, ConfigureAwait(false), itd. Wadą jest to, że jesteś dodając trochę narzut. Również to podejście działa tylko na .NET 4.5; kontekst logicznego wywołania w .NET 4.0 nie jest zgodny z async i nie będzie działał prawidłowo pod numerem .

Aktualizacja: Wydałem NuGet package (described on my blog), który wykorzystuje PostSharp do automatycznego wprowadzania popychnięć i wyskakuje. Tak więc uzyskanie dobrego śladu powinno być teraz o wiele prostsze.

+4

Dzięki Stephen. Dodałbym do twojej notatki o niepewnych stosach wywołań, które w przypadku optymalizacji optymalizacji lub optymalizacji wywołań końcowych, rzeczą na wierzchu stosu wywołań może być * wywołujący * dzwoniący. Nie możesz więc polegać na nim w wielu "prostych" scenariuszach. –

3

Jest ładne rozszerzenie nuget do tego przez króla async/oczekujących.

https://www.nuget.org/packages/AsyncStackTraceEx/

trzeba zmienić połączenia czekają od

Await DownloadAsync(url) 

do

Await DownloadAsync(url).Log() 

Ostatecznie w bloku catch, po prostu zadzwoń

ex.StackTraceEx() 

Jedna ważna uwaga: Ta metoda może być wywołana tylko raz, a ex.StackTrace nie może być wcześniej oceniany. Wygląda na to, że stos można odczytać tylko raz.

Powiązane problemy