Лучший способ запустить и забыть асинхронный код внутри TransactionScope
Я делаю некоторые вещи внутри блока using для объекта TransactionScope. В какой-то момент я хотел вызвать некоторый асинхронный код, запустив и забыв (я не хочу ждать результата, и мне не интересно, что происходит во время этого вызова), и я хотел, чтобы этот код не был частью транзакция (используя TransactionScopeOption.Suppress
опция).
Поэтому изначально я сделал что-то похожее на methodFails
что я прокомментировал в коде ниже. Это дало мне приятное "System.InvalidOperationException:" TransactionScope неправильно вложен "". Я искал в SO, чтобы кто-то имел подобные проблемы, и нашел этот Вопрос, где ответ ZunTzu дал мне идею для method1
с помощью TransactionScopeAsyncFlowOption.Enabled
вариант, который работает, как я ожидал methodFails
но без исключения.
Затем я подумал об альтернативе, которую я положил в method2
это состоит в том, чтобы поместить асинхронный код в третий метод (method3
Запусти стрельбу и забудь пока TransactionScopeOption.Suppress
опция сохраняется в не асинхронном режиме method2
, И этот подход, кажется, работает так же хорошо, как method1
в моем примере программы.
Итак, мой вопрос: какой подход лучше, method1
или же method2
или, может быть, третий, о котором я даже не думал? Я склоняюсь к method1
потому что это звучит как "люди, создающие класс TransactionScope, по какой-то причине помещают эту TransactionScopeAsyncFlowOption". Но то, что TransactionScopeAsyncFlowOption.Enabled
не является значением по умолчанию для TransactionScope, заставляет меня думать, что, возможно, это может привести к снижению производительности, и может быть особый случай, когда я могу сохранить это снижение производительности.
Пример кода:
class Program
{
static void Main(string[] args)
{
using (TransactionScope scope1 = new TransactionScope())
{
// Do some stuff in scope1...
// Start calls that could execute async code
//Task a = methodFails(); // This commented method would launch exception: System.InvalidOperationException: 'TransactionScope nested incorrectly'
Task b = method1(); // Fire and forget
method2();
// Rest of stuff in scope1 ...
}
Console.ReadLine();
}
static async Task methodFails()
{
//Start of non-transactional section
using (TransactionScope scope2 = new TransactionScope(TransactionScopeOption.Suppress))
{
//Do non-transactional work here
Console.WriteLine("Hello World 0.1!!");
await Task.Delay(10000);
Console.WriteLine("Hello World 0.2!!");
}
//Restores ambient transaction here
Console.WriteLine("Hello World 0.3!!");
}
static async Task method1()
{
//Start of non-transactional section
using (TransactionScope scope2 = new TransactionScope(TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled))
{
//Do non-transactional work here
Console.WriteLine("Hello World 1.1!!");
await Task.Delay(10000);
Console.WriteLine("Hello World 1.2!!");
}
//Restores ambient transaction here
Console.WriteLine("Hello World 1.3!!");
}
static void method2()
{
//Start of non-transactional section
using (TransactionScope scope2 = new TransactionScope(TransactionScopeOption.Suppress))
{
//Do non-transactional work here
Task ignored = method3(); // Fire and forget
}
//Restores ambient transaction here
Console.WriteLine("Hello World 2.2!!");
}
static async Task method3()
{
//Do non-transactional work here
Console.WriteLine("Hello World 2.1!!");
await Task.Delay(10000);
Console.WriteLine("Hello World 2.3!!");
}
}
2 ответа
Но тот факт, что TransactionScopeAsyncFlowOption.Enabled не является значением по умолчанию для TransactionScope, заставляет меня думать, что, возможно, при этом включается снижение производительности, а запуск и удаление могут быть особым случаем, когда я могу сохранить это снижение производительности.
TransactionScopeAsyncFlowOption.Enabled
был введен в целях обратной совместимости, когда они исправили ошибку. Как ни странно, вы не извлечете выгоду из исправления ошибки, если не "включите", установив этот флаг. Они сделали это таким образом, чтобы исправление ошибки не нарушало существующий код, который полагался на поведение ошибки.
В этой статье:
Возможно, вы этого не знаете, но версия.NET Framework 4.5.0 содержит серьезную ошибку, касающуюся System.Transactions.TransactionScope и ее поведения в async / await. Из-за этой ошибки TransactionScope не может проникнуть в ваши асинхронные продолжения. Это потенциально меняет контекст многопоточности транзакции, вызывая исключения при удалении области транзакции.
Это большая проблема, поскольку делает написание асинхронного кода с транзакциями чрезвычайно подверженным ошибкам.
Хорошей новостью является то, что в рамках.NET Framework 4.5.1 корпорация Майкрософт выпустила исправление для этой ошибки "асинхронного продолжения". Дело в том, что разработчики, подобные нам, теперь должны явно подписаться, чтобы получить это новое поведение. Давайте посмотрим, как это сделать.
- TransactionScope, обертывающий асинхронный код, должен указать TransactionScopeAsyncFlowOption.Enabled в своем конструкторе.
Вы можете вызвать ваши асинхронные методы в пределах HostingEnvironment.QueueBackgroundWorkItem
вызов.
HostingEnvironment.QueueBackgroundWorkItem(async cancellationToken =>
{
await LongRunningMethodAsync();
});
QueueBackgroundWorkItem суммируется следующим образом:
Метод HostingEnvironment.QueueBackgroundWorkItem позволяет планировать небольшие фоновые рабочие элементы. ASP.NET отслеживает эти элементы и не позволяет IIS внезапно завершать рабочий процесс до тех пор, пока все фоновые рабочие элементы не будут завершены.