C# GUI线程上的IDbConnection.BeginTransaction,使用async/await,导致多个调用死锁
我有一个问题,我完全理解,但正在努力解决C# GUI线程上的IDbConnection.BeginTransaction,使用async/await,导致多个调用死锁,c#,.net,multithreading,transactions,async-await,C#,.net,Multithreading,Transactions,Async Await,我有一个问题,我完全理解,但正在努力解决 我有一个使用async/wait的事件循环 我在所有数据库操作上使用async/await,所有continuations都将运行事件循环的同一个线程登录 我还需要使用事务,在它的生命周期中,它将有一些其他的延续 这样说,考虑这个代码。 public异步任务UpdateItem(int-mediateItemId) { 使用(var connection=\u dataService.OpenDbConnection()) { 使用(var transa
async
/wait
的事件循环async
/await
,所有continuations都将运行事件循环的同一个线程登录这样说,考虑这个代码。
public异步任务UpdateItem(int-mediateItemId)
{
使用(var connection=\u dataService.OpenDbConnection())
{
使用(var transaction=connection.BeginTransaction())
{
var item=wait connection.SingleByIdAsync(mediaItemId);
item.Index++;
等待连接。更新同步(项目);
Commit();
}
}
}
此方法的调用方来自主事件循环
我首先创建一个事务。使用SqlLite,这实际上是一个数据库级互斥。这意味着,当事务正在进行时,对BeginTransaction
的其他调用将被阻止
现在,考虑这个方法被多次调用,快速连续。在
wait
ingSingleByIdAsync
之后,第二个调用将尝试BeginTransaction
,但它将在那里等待,直到第一个事务完成。这是意料之中的,只是它会阻止主事件循环,阻止任何进一步的继续发生,使第一个事务保持打开状态
轰,死锁
如果存在一个IDbConnection.BeginTransactionAsync
,则可以解决这个问题,而实际上没有。这样我就可以跳出事件循环,并在事务成功打开后继续
,请考虑下面的修复:
public异步任务UpdateItem(int-mediateItemId)
{
使用(var connection=\u dataService.OpenDbConnection())
{
//请注意,我们正在等待交易的开始。
使用(var transaction=wait Task.Run(()=>connection.BeginTransaction())
{
var item=wait connection.SingleByIdAsync(mediaItemId);
item.Index++;
等待连接。更新同步(项目);
Commit();
}
}
}
话虽如此,您认为打开未打开数据库连接的线程的事务有什么害处吗?为什么没有IDbConnection.BeginTransactionAsync
?wait Task.Run(()=>connection.BeginTransaction())
是可接受的解决方案吗
这样说,您是否看到打开未打开数据库连接的线程事务有什么危害
这里有几个考虑因素。第一个是在与打开db连接的线程不同的线程上打开事务。另一种情况是,您在与打开事务的线程不同的线程上提交和处理事务
wait Task.Run(()=>connection.BeginTransaction())是可接受的解决方案吗
“这是个问题吗?”问题只能由(客户机)数据库提供商回答
如果要确保不会出现问题,可以包括您自己的互斥:
private static readonly SemaphoreSlim _mutex = new SemaphoreSlim(1);
public async Task UpdateItem(int mediaItemId)
{
using (var connection = _dataService.OpenDbConnection())
{
await _mutex.WaitAsync();
try
{
using (var transaction = await connection.BeginTransaction())
{
var item = await connection.SingleByIdAsync<MediaItem>(mediaItemId);
item.Index++;
await connection.UpdateAsync(item);
transaction.Commit();
}
}
finally
{
_mutex.Release();
}
}
}
private static readonly SemaphoreSlim\u mutex=new SemaphoreSlim(1);
公共异步任务更新项(int mediaItemId)
{
使用(var connection=\u dataService.OpenDbConnection())
{
wait_mutex.WaitAsync();
尝试
{
使用(var transaction=await connection.BeginTransaction())
{
var item=wait connection.SingleByIdAsync(mediaItemId);
item.Index++;
等待连接。更新同步(项目);
Commit();
}
}
最后
{
_mutex.Release();
}
}
}
或
private static readonly SemaphoreSlim\u mutex=new SemaphoreSlim(1);
公共异步任务更新项(int mediaItemId)
{
使用(var connection=\u dataService.OpenDbConnection())
使用(_mutex.LockAsync())
使用(var transaction=await connection.BeginTransaction())
{
var item=wait connection.SingleByIdAsync(mediaItemId);
item.Index++;
等待连接。更新同步(项目);
Commit();
}
}
能否显示“事件循环”。NET通常没有这样的东西,至少在这里不是这样建议的。它是基于油嘴滑舌的。这很难描述,但我正在使用qmlnet。请找到一种方式来传达情况的本质。是否有一个线程具有同步上下文?是的,有一个线程具有事件循环,具有自定义同步上下文,可将继续发送到单个线程上运行的事件循环。谢谢!我刚刚研究了.NET sqlite实现,特别是sqlite3\u exec
,它是线程安全的。我将在另一条线上等待。
private static readonly SemaphoreSlim _mutex = new SemaphoreSlim(1);
public async Task UpdateItem(int mediaItemId)
{
using (var connection = _dataService.OpenDbConnection())
using (_mutex.LockAsync())
using (var transaction = await connection.BeginTransaction())
{
var item = await connection.SingleByIdAsync<MediaItem>(mediaItemId);
item.Index++;
await connection.UpdateAsync(item);
transaction.Commit();
}
}