77

WebApi アプリケーションに次のサンプル コードをセットアップしました。

[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!");
}

残念ながら、GetValueAction がヒットすると、返されるスタック トレースは次のようになります。

    " 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"

したがって、トレースで GetValue2 と GetValue を取得 (マングル) しますが、GetValueAction については言及していません。私は何か間違ったことをしていますか?より完全なスタック トレースを取得する別のパターンはありますか?

編集:私の目標は、スタック トレースに依存するコードを記述することではなく、非同期メソッドのエラーをデバッグしやすくすることです。

4

3 に答える 3

38

まず、スタックトレースは、ほとんどの人が考えていることを実行しません。これらはデバッグ中に役立ちますが、特にASP.NETでの実行時の使用を目的としたものではありません。

また、スタックトレースは、技術的には、コードがどこから来たのかではなく、コードがどこに戻っているのかに関するものです。単純な(同期)コードでは、2つは同じです。コードは常にそれを呼び出したメソッドに戻ります。ただし、非同期コードでは、これら2つは異なります。繰り返しになりますが、スタックトレースは次に何が起こるかを示しますが、過去に何が起こったかに関心があります。

したがって、スタックフレームはニーズに対する正しい答えではありません。Eric Lippertは、ここでの彼の回答でこれをよく説明しています

@ColeCampbellがリンクしているMSDNの記事では、コードを使用て「因果関係チェーン」(コードの出所)を追跡する1つの方法について説明していasyncます。残念ながら、そのアプローチは制限されています(たとえば、フォーク/結合シナリオを処理しません)。ただし、Windowsストアアプリケーションで機能するのは、私が知っている唯一のアプローチです。

完全な.NET4.5ランタイムを備えたASP.NETを使用しているため、死傷者チェーンを追跡するためのより強力なソリューションである論理呼び出しコンテキストにアクセスできます。asyncただし、メソッドは「オプトイン」する必要があるため、スタックトレースの場合のように無料で取得することはできません。まだ公開されていないブログ投稿にこれを書いたので、プレビューが表示されます。:)

次のように、論理呼び出しコンテキストを中心に自分で呼び出しの「スタック」を構築できます。

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入手できます)。その後、次のように使用できます。

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

このアプローチの良いところは、フォーク/ジョイン、カスタム待機可能コードなど、すべての コードで機能することです。欠点は、オーバーヘッドがいくらか追加されることです。また、このアプローチは.NET4.5でのみ機能します。.NET 4.0の論理呼び出しコンテキストは認識されておらず、正しく機能しません。asyncConfigureAwait(false)async

更新: PostSharpを使用してプッシュとポップを自動的に挿入するNuGetパッケージ(ブログで説明)をリリースしました。したがって、適切なトレースを取得することは、今でははるかに簡単になるはずです。

于 2013-03-14T14:51:47.913 に答える
5

これには、async/await king による素晴らしい nuget 拡張機能があります。

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

await 呼び出しを次から変更する必要があります。

Await DownloadAsync(url)

Await DownloadAsync(url).Log()

最後に、catch ブロックで、単に呼び出します

ex.StackTraceEx()

1 つの重要な注意: このメソッドは 1 回だけ呼び出すことができ、ex.StackTrace は前に評価してはなりません。スタックは一度しか読み取れないようです。

于 2015-11-20T17:10:33.720 に答える