【发布时间】:2019-01-30 17:33:47
【问题描述】:
我遇到了一个问题,我完全理解,但正在努力解决。
- 我有一个使用
async/await的事件循环。 - 我在所有数据库操作中使用
async/await,所有的延续都登陆所有运行事件循环的同一个线程。 - 我还需要使用事务,它在其生命周期内会有一些其他的延续。
话虽如此,请考虑这段代码。
public async Task UpdateItem(int mediaItemId)
{
using (var connection = _dataService.OpenDbConnection())
{
using (var transaction = connection.BeginTransaction())
{
var item = await connection.SingleByIdAsync<MediaItem>(mediaItemId);
item.Index++;
await connection.UpdateAsync(item);
transaction.Commit();
}
}
}
此方法的调用者来自主事件循环。
我首先创建一个事务。使用 SqlLite,这实际上是一个数据库级互斥体。这意味着当一个事务正在进行时,对BeginTransaction 的其他调用将被阻塞。
现在,考虑多次快速连续调用此方法。在awaiting SingleByIdAsync 之后,第二个调用将尝试调用BeginTransaction,但它会在那里等到第一个事务完成。这是意料之中的,除了它会阻塞主事件循环,阻止任何进一步的继续发生,让第一个事务保持打开状态。
繁荣,僵局。
如果有IDbConnection.BeginTransactionAsync,这将得到解决,但没有。那我可以跳过事件循环,一旦事务成功打开就继续。
因此,请考虑以下修复:
public async Task UpdateItem(int mediaItemId)
{
using (var connection = _dataService.OpenDbConnection())
{
// Note that we are awaiting the opening of the transaction.
using (var transaction = await Task.Run(() => connection.BeginTransaction()))
{
var item = await connection.SingleByIdAsync<MediaItem>(mediaItemId);
item.Index++;
await connection.UpdateAsync(item);
transaction.Commit();
}
}
}
话虽如此,您认为打开未打开数据库连接的线程的事务有什么害处吗?为什么没有IDbConnection.BeginTransactionAsync? await Task.Run(() => connection.BeginTransaction()) 是可接受的解决方案吗?
【问题讨论】:
-
你能显示“事件循环”吗? .NET 通常没有这样的东西,至少不是这里建议的方式。
-
它是基于 glib 的。很难描述,但我使用的是 QmlNet
-
请想办法表达情况的本质。是否有一个线程具有同步上下文?
-
是的,一个线程,带有一个事件循环,带有一个自定义同步上下文,可以将延续分派到在单个线程上运行的事件循环上。
标签: c# .net multithreading transactions async-await