Лучший способ запустить и забыть асинхронный код внутри 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 внезапно завершать рабочий процесс до тех пор, пока все фоновые рабочие элементы не будут завершены.

Другие вопросы по тегам