Como muchos saben, TransactionScope se olvidó cuando se introdujo el patrón de await async en .Net. Se rompieron si intentábamos usar alguna llamada en await dentro del alcance de una transacción.
Ahora esto está solucionado gracias a una opción de constructor de alcance .
Pero me parece que todavía falta una pieza, al menos no puedo encontrar cómo hacerlo de una manera simple de "alcance de transacción": ¿cómo esperar la confirmación o reversión de un alcance?
La confirmación y la reversión también son operaciones de E/S, deberían estar disponibles. Pero dado que ocurren en la disposición del alcance, tenemos que esperar la disposición. Eso no es factible sin tener IAsyncDisposable implementado por ámbitos de transacción, que no es el caso actualmente.
También eché un vistazo a la interfaz System.Transactions.Transaction : tampoco hay métodos disponibles allí.
Entiendo que comprometerse y retroceder es casi solo enviar una bandera a la base de datos, por lo que debería ser rápido. Pero con transacciones distribuidas, eso podría ser menos rápido. Y de todos modos, eso sigue siendo un bloqueo de IO.
Acerca de los casos distribuidos, recuerde que esto puede desencadenar una confirmación de dos fases. En algunos casos, se alistan recursos duraderos adicionales durante la primera fase (preparación). Entonces, por lo general, significa que se emiten algunas consultas adicionales en relación con los recursos recientemente reclutados. Todo lo que sucede durante el compromiso.
Entonces, ¿hay alguna forma de esperar un alcance de transacción? ¿O un System.Transactions.Transaction en su lugar?
Nota: No considero que esto sea un duplicado de " ¿Es posible confirmar/revertir SqlTransaction en modo asíncrono? ". SqlTransaction son más limitadas que las transacciones del sistema. Solo pueden dirigirse a SQL-Server y nunca se distribuyen. Algunas otras transacciones tienen métodos asíncronos, como Npgsql . Ahora, para tener métodos asíncronos en los ámbitos de transacción/transacción del sistema, es posible que DbTransaction deba tener métodos asíncronos. (No conozco los aspectos internos de la transacción del sistema, pero tal vez esté usando este contrato ADO.NET. Sin embargo, la forma en que enlistamos la conexión en la transacción del sistema me hace pensar que no lo usa).
Actualización: DbTransaction los tiene en .Net Core 3.0, vea #35012 (especialmente gracias a Roji ).
No hay forma de implementarlo hasta ahora. pero trabajan en eso
Tal vez sea una respuesta tardía, pero lo que desea básicamente se reduce a una especie de azúcar sintáctico que puede crear fácilmente por su cuenta.
Generalizando su problema, implementé una sintaxis de "uso asíncrono", que permite que tanto el cuerpo como la parte "desechar" del "uso" estén disponibles. Así es como se ve:
async Task DoSomething() { await UsingAsync.Do( // this is the disposable usually passed to using(...) new TransactionScope(TransactionScopeAsyncFlowOption.Enabled), // this is the body of the using() {...} async transaction => { await Task.Delay(100); // do any async stuff here... transaction.Complete(); // mark transaction for Commit } // <-- the "dispose" part is also awaitable ); }La implementación es tan simple como esto:
public static class UsingAsync { public static async Task Do<TDisposable>( TDisposable disposable, Func<TDisposable, Task> body) where TDisposable : IDisposable { try { await body(disposable); } finally { if (disposable != null) { await Task.Run(() => disposable.Dispose()); } } } } Hay una diferencia en el manejo de errores, en comparación con la cláusula de using normal. Al usar UsingAsync.Do , cualquier excepción lanzada por el cuerpo o el dispositivo de eliminación se envolverá dentro de una AggregateException . Esto es útil cuando tanto el cuerpo como el dispositivo arrojan una excepción, y ambas excepciones se pueden examinar en una AggregateException . Con la cláusula de using regular, solo se capturará la excepción lanzada por dispose, a menos que el cuerpo esté explícitamente envuelto en try..catch .