Estoy haciendo algunas cosas dentro de un bloque de uso para un objeto TransactionScope. En algún momento quise llamar a algún código asíncrono disparando y olvidar (no quiero esperar el resultado, y no estoy interesado en lo que sucede durante esa llamada) y quería que ese código no fuera parte del transacción (usando la opción TransactionScopeOption.Suppress
).
Así que inicialmente hice algo similar al methodFails
que he comentado en el código a continuación. Me dio un buen "System.InvalidOperationException: 'TransactionScope anidado incorrectamente'" . Busqué en SO alguien que tuviera problemas similares y encontré esta Pregunta donde la respuesta de ZunTzu me dio la idea de method1
usando la opción TransactionScopeAsyncFlowOption.Enabled
, que funciona como esperaba para methodFails
pero sin excepción.
Entonces pensé en una alternativa que puse en method2
que consiste en poner el código asíncrono en un tercer método (method3
) llamado disparando y olvidando mientras se mantiene la opción TransactionScopeOption.Suppress
en el no asincrónico method2
. Y este enfoque parece funcionar tan bien como method1
en mi programa de muestra.
Entonces, mi pregunta es: ¿qué enfoque es mejor, method1
o method2
, o tal vez un tercero en el que no he pensado? Me inclino por method1
porque parece que "las personas que hacen la clase TransactionScope ponen ese TransactionScopeAsyncFlowOption allí por una razón". Pero el hecho de que TransactionScopeAsyncFlowOption.Enabled
no sea el valor predeterminado para TransactionScope me hace pensar que tal vez haya un impacto en el rendimiento al habilitarlo, y disparar y olvidar puede ser un caso especial en el que puedo guardar ese impacto en el rendimiento.
El código de muestra:
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 respuestas
Pero el hecho de que TransactionScopeAsyncFlowOption.Enabled no sea el valor predeterminado para TransactionScope me hace pensar que tal vez haya un impacto en el rendimiento al habilitarlo, y disparar y olvidar puede ser un caso especial en el que puedo guardar ese impacto en el rendimiento.
TransactionScopeAsyncFlowOption.Enabled
se introdujo por motivos de compatibilidad con versiones anteriores cuando arreglaron un error. Curiosamente, no se beneficia de la corrección de errores a menos que "suscriba" estableciendo esta marca. Lo hicieron de esa manera para que la corrección de errores no rompiera ningún código existente que se basara en el comportamiento defectuoso.
En este artículo:
Es posible que no lo sepa, pero la versión 4.5.0 de .NET Framework contiene un error grave con respecto a System.Transactions.TransactionScope y cómo se comporta con async / await. Debido a este error, TransactionScope no puede fluir hacia sus continuaciones asincrónicas. Esto potencialmente cambia el contexto de subprocesos de la transacción, lo que provoca que se produzcan excepciones cuando se elimina el alcance de la transacción.
Este es un gran problema, ya que hace que escribir código asincrónico que involucre transacciones sea extremadamente propenso a errores.
La buena noticia es que, como parte de .NET Framework 4.5.1, Microsoft lanzó la solución para ese error de "continuación asincrónica". Lo que pasa es que los desarrolladores como nosotros ahora deben optar explícitamente para obtener este nuevo comportamiento. Echemos un vistazo a cómo hacerlo.
- Un código asincrónico de empaquetado de TransactionScope debe especificar TransactionScopeAsyncFlowOption.Enabled en su constructor.
Puede llamar a sus métodos asíncronos dentro de una llamada HostingEnvironment.QueueBackgroundWorkItem
.
HostingEnvironment.QueueBackgroundWorkItem(async cancellationToken =>
{
await LongRunningMethodAsync();
});
QueueBackgroundWorkItem se resume de la siguiente manera:
El método HostingEnvironment.QueueBackgroundWorkItem le permite programar pequeños elementos de trabajo en segundo plano. ASP.NET realiza un seguimiento de estos elementos y evita que IIS termine abruptamente el proceso de trabajo hasta que se hayan completado todos los elementos de trabajo en segundo plano.
Nuevas preguntas
c#
C # (pronunciado "see sharp") es un lenguaje de programación multi-paradigma de alto nivel, estáticamente tipado desarrollado por Microsoft. El código C # generalmente se dirige a la familia de herramientas y tiempos de ejecución .NET de Microsoft, que incluyen .NET Framework, .NET Core y Xamarin, entre otros. Use esta etiqueta para preguntas sobre el código escrito en las especificaciones formales de C # o C #.