Отладка исключений в Async/Await (стек вызовов)

Я использую Async/Await, чтобы освободить свой пользовательский интерфейс и выполнить многопоточность. Теперь у меня проблема, когда я ударил исключение. Call Stack моих частей Async всегда начинается с ThreadPoolWorkQue.Dipatch(), что не очень помогает мне.

Я нашел MSDN-статью Андрей Стасюк. Async Causality Chain Tracking об этом, но, насколько я понимаю, это не готовое к использованию решение.

Каков наилучший/самый простой способ отладки, если вы используете многопоточность с помощью Async/Await?

Ответ 1

В статье, которую вы нашли, хорошо объясняется, почему стеки вызовов не работают так, как думают многие из нас. Технически, стек вызовов сообщает нам, где код возвращается после текущего метода. Другими словами, стек вызовов "где код идет", а не "откуда пришел код".

Интересно, что в статье упоминается решение, но не излагает его. У меня сообщение в блоге, которое объясняет подробное описание CallContext. По сути, вы используете контекст логического вызова для создания своего собственного "диагностического контекста".

Мне нравится решение CallContext лучше, чем решение, представленное в статье, потому что оно работает во всех формах кода async (включая код fork/join, например Task.WhenAll).

Это лучшее решение, которое я знаю (кроме выполнения чего-то действительно сложного, например, подключения к API профилирования). Оговорки подхода CallContext:

  • Он работает только на .NET 4.5. Нет поддержки для приложений Windows Store,.NET 4.0 и т.д.
  • Вам нужно "вручную" ввести свой код вручную. Нет никакого способа, чтобы AFAIK автоматически вводил его.
  • Исключения не фиксируют контекст логического вызова автоматически. Таким образом, это решение отлично работает, если вы входите в отладчик, когда выбрасываются исключения, но это не так полезно, если вы просто ловите исключения в другом месте и регистрируете их.

Код (зависит от неизменяемых коллекций NuGet library):

public static class MyStack
{
    private static readonly string name = Guid.NewGuid().ToString("N");

    private static ImmutableStack<string> CurrentContext
    {
        get
        {
            var ret = CallContext.LogicalGetData(name) as ImmutableStack<string>;
            return ret ?? ImmutableStack.Create<string>();
        }

        set
        {
            CallContext.LogicalSetData(name, value);
        }
    }

    public static IDisposable Push([CallerMemberName] string context = "")
    {
        CurrentContext = CurrentContext.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;
        }
    }

    // Keep this in your watch window.
    public static string CurrentStack
    {
        get
        {
            return string.Join(" ", CurrentContext.Reverse());
        }
    }
}

Использование:

static async Task SomeWorkAsync()
{
    using (MyStack.Push()) // Pushes "SomeWorkAsync"
    {
        ...
    }
}

Обновление: Я выпустил пакет NuGet (описанный в моем блоге), который использует PostSharp для ввода нажатий и всплывает автоматически. Поэтому получить хороший след теперь будет намного проще.