Асинхронно совершать или откатывать область транзакций
Как известно, TransactionScope
были забыты, когда шаблон async
await
был введен в .Net. Они были сломаны, если мы пытались использовать какой-либо вызов await
внутри области транзакции.
Теперь это исправлено благодаря конструктору возможностей > .
Но мне кажется, что все еще есть недостающая часть, по крайней мере, я не могу найти, как это сделать в простой форме транзакции вроде: как ждать фиксации или отката области?
Commit и rollback также являются операциями ввода-вывода, они должны быть ожидаемыми. Но так как они происходят по распоряжению областью, нам придется ждать распоряжения. Это не выглядит выполнимым (или практичным с шаблоном using
).
Я также посмотрел на System.Transactions.Transaction
: нет ожидаемых методов.
Я понимаю, что фиксация и откат - это просто отправка флага в базу данных, поэтому она должна быть быстрой. Но с распределенными транзакциями это может быть менее быстрым. И вообще, это все еще некоторая блокировка IO.
О распределенных случаях, помните, что это может привести к фиксации двух фаз. В некоторых случаях на первом этапе (подготовка) зачисляются дополнительные долговременные ресурсы. Это обычно означает, что некоторые дополнительные запросы выдаются против тех, кто недавно зачислен в ресурсы. Все, что происходит во время фиксации.
Итак, есть ли способ ждать области транзакции? Или вместо System.Transactions.Transaction
?
Примечание: Я не считаю, что это дубликат "Возможно ли совершить/отменить SqlTransaction в асинхронном?". SqlTransaction
более ограничены, чем системные транзакции. Они могут обращаться только к SQL-Server и никогда не распространяются. Некоторые другие транзакции имеют асинхронные методы, такие как Npgsql. Теперь для методов async для транзакций/системных транзакций DbTransaction
может потребоваться использовать методы async. (Я не знаю внутренних системных транзакций, но возможно, используя этот контракт ADO.NET. Способ подключения к системной транзакции позволяет мне думать, что он не использует его, хотя.)
Ответы
Ответ 1
Возможно, поздний ответ, но то, что вы хотите, в основном сводится к синтаксическому сахару, который можно легко создать самостоятельно.
Обобщая вашу проблему, я применил синтаксис "async using", который позволяет обеим сторонам и "распоряжаться" частью "использования" быть ожидаемым. Вот как это выглядит:
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
);
}
Реализация такая же простая:
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());
}
}
}
}
Существует разница в обработке ошибок по сравнению с обычным предложением using
. При использовании UsingAsync.Do
любое исключение, созданное телом или распоряжением, будет обернуто внутри AggregateException
. Это полезно, когда и тело, и распоряжение бросают исключение, и оба исключения могут быть рассмотрены в AggregateException
. С помощью обычного предложения using
будет исключено только исключение, созданное командой dispose, если тело явно не упаковано в try..catch
.