3

我正在为 TransactionScope 对象的 using 块内做一些事情。在某些时候,我想通过触发并忘记来调用一些异步代码(我不想等待结果,而且我对该调用期间发生的事情不感兴趣)并且我希望该代码不属于交易(通过使用TransactionScopeOption.Suppress选项)。

所以最初我做了一些类似于methodFails我在下面的代码中评论过的东西。它给了我一个很好的"System.InvalidOperationException: 'TransactionScope nested wrong'"。我在 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!!");
        }
    }
4

2 回答 2

4

But the fact that TransactionScopeAsyncFlowOption.Enabled is not the default for a TransactionScope makes me think that maybe there is a performance hit by enabling that, and fire-and-forget may be a special case where I can save that performance hit.

TransactionScopeAsyncFlowOption.Enabled was introduced for backward compatibility purposes when they fixed a bug. Strangely, you don't benefit from the bug fix unless you "opt in" by setting this flag. They did it that way so the bug fix didn't break any existing code that relied on the buggy behavior.

In this article:

You might not know this, but the 4.5.0 version of the .NET Framework contains a serious bug regarding System.Transactions.TransactionScope and how it behaves with async/await. Because of this bug, a TransactionScope can't flow through into your asynchronous continuations. This potentially changes the threading context of the transaction, causing exceptions to be thrown when the transaction scope is disposed.

This is a big problem, as it makes writing asynchronous code involving transactions extremely error-prone.

The good news is that as part of the .NET Framework 4.5.1, Microsoft released the fix for that "asynchronous continuation" bug. The thing is that developers like us now need to explicitly opt-in to get this new behavior. Let's take a look at how to do just that.

  • A TransactionScope wrapping asynchronous code needs to specify TransactionScopeAsyncFlowOption.Enabled in its constructor.
于 2018-02-14T08:38:18.030 回答
3

您可以在调用中调用异步方法HostingEnvironment.QueueBackgroundWorkItem

HostingEnvironment.QueueBackgroundWorkItem(async cancellationToken =>
{
    await LongRunningMethodAsync();
});

QueueBackgroundWorkItem总结如下:

The HostingEnvironment.QueueBackgroundWorkItem method lets you schedule small background work items. ASP.NET tracks these items and prevents IIS from abruptly terminating the worker process until all background work items have completed.

于 2018-02-14T08:26:10.090 回答