Как мы можем создать callcontext для асинхронных методов .net?

В синхронной среде легко создать контекст с ограниченной областью действия, который позволит вам присоединить внешний контекст к текущему потоку. Примерами этого являются текущий TransactionScope или статический контекст ведения журнала потока.

using (new MyContext(5))
    Assert.Equal(5, MyContext.Current);

Assert.Equal(null, MyContext.Current);

Контекст легко реализовать с помощью комбинации IDisposable и поля, статического для потока.

Очевидно, что при использовании асинхронных методов это не работает, потому что контекст основан на статическом поле потока. Итак, это не удается:

using (new MyContext(5))
{
    Assert.Equal(5, MyContext.Current);

    await DoSomethingAsync();

    Assert.Equal(5, MyContext.Current);
}

И, конечно, мы также хотели бы, чтобы контекст передавался асинхронным методам в цепочке вызовов, так что это тоже должно работать:

using (new MyContext(5))
{
    Assert.Equal(5, MyContext.Current);

    await AssertContextIs(5);
}

У кого-нибудь есть идеи, как это можно реализовать? Потеря внеполосного контекста при использовании шаблона async/await делает некоторые фрагменты кода действительно уродливыми.

Подумайте об асинхронных вызовах WebAPI, когда вы хотели бы использовать контекст на основе запроса для целей ведения журнала. Вы хотите, чтобы ваш регистратор глубоко в стеке вызовов знал об идентификаторе запроса без необходимости передавать этот идентификатор запроса через стек вызовов с использованием параметров.

Спасибо за любую помощь!


person Jochen    schedule 24.09.2014    source источник
comment
Я не очень понимаю, что не работает во втором примере. Вы все еще находитесь в контексте вашего using заявления после того, как вы await DoSomethingAsync.   -  person Yuval Itzchakov    schedule 24.09.2014


Ответы (2)


Лучшее решение — использовать HttpContext.Current.Items в ASP.NET или IOwinRequest.[Get|Set] в OWIN. Да, это означает передачу объекта запроса везде, что имеет смысл, если вы считаете, что ваше «контекстное значение» принадлежит этому запросу. Если вам не нравится "библиотечный" метод, знающий о OWIN, то легко написать объект "контекстная оболочка" и вместо этого передать его.

Однако, если вы уверены, что не хотите ничего передавать, вы можете используйте LogicalCallContext с неизменяемыми данными, как я описал в своем блоге. Если вас интересует только ведение журнала, вы можете найти мою библиотеку AsyncDiagnostics полезно - он использует PostSharp для вставки имен методов, и вы также можете добавить свою собственную информацию в «асинхронный стек».

person Stephen Cleary    schedule 24.09.2014
comment
Контекст логического вызова — это именно то, что я искал. Спасибо! - person Jochen; 25.09.2014

Если вы находитесь в размещенной среде (вы говорите о WebAPI), вы должны использовать контекст запроса (HttpContext.Current), а не поток, поскольку вы не управляете потоками и не знаете, когда/будет ли доступен исходный поток, и как вернуться к нему.

Контекст запроса правильно обрабатывается с помощью ключевого слова await.

person Guillaume    schedule 24.09.2014
comment
Во-первых, доступность HttpContext.Current не гарантируется (только для приложений, размещенных в IIS). Итак, это зависимость от System.Web, на которую я определенно не хочу полагаться. Но, что более важно, мой вопрос носит более общий характер, идентификатор запроса был просто примером одного варианта использования. Я определенно ищу общее решение проблемы: передать контекст OOB при использовании шаблона async/await. - person Jochen; 24.09.2014
comment
async/await полагается на SynchronizationContext, а не на потоки. Таким образом, решение заключается в том, чтобы привязать ваши данные к контексту, чтобы у вас была зависимость от вашего контекста. Если вы хотите избежать этого, избегайте использования контекста и предоставляйте значения в качестве аргумента. - person Guillaume; 24.09.2014