Multithreading 锁定异步方法中的竞速条件
我正在创建一个Blazor服务器应用程序,它在加载控件时执行一些Multithreading 锁定异步方法中的竞速条件,multithreading,thread-safety,.net-5,Multithreading,Thread Safety,.net 5,我正在创建一个Blazor服务器应用程序,它在加载控件时执行一些asyncDB访问 我创建了一个DB locker类,以确保在使用Sqlite时不会同时打开两个事务。目标是控制并行事务的数量——在这里,我将其限制为1 我在这里遇到了一个赛车状态错误,但我无法发现实际发生了什么以及为什么我得到threadSyncCount>1 我的DB locker类如下所示: public static class DBLocker { static Random rnd = new Random();
async
DB访问
我创建了一个DB locker类,以确保在使用Sqlite时不会同时打开两个事务。目标是控制并行事务的数量——在这里,我将其限制为1
我在这里遇到了一个赛车状态错误,但我无法发现实际发生了什么以及为什么我得到threadSyncCount>1
我的DB locker类如下所示:
public static class DBLocker {
static Random rnd = new Random();
static object _lockObject = new object();
static int threadSyncCount = 0;
public static bool WaitForDBAccess(bool forceWait = false) {
bool lockWasTaken = false;
TimeSpan tSpan = new TimeSpan(0, 0, 0, 500);
int delayCount = 0;
// --- make sure we get the lock
while (!lockWasTaken) {
if (threadSyncCount == 0) {
Monitor.TryEnter(_lockObject, tSpan, ref lockWasTaken);
}
else {
await Task.Delay(rnd.Next(50, 150));
}
++delayCount;
// --- sometimes it's just OK to leave without getting the lock => just skip waiting,
// the caller method will just return without any DB access in this case!
if (!forceWait && delayCount > 5) {
// --- NB: this actually never happens!
if (lockWasTaken) Monitor.Exit(_lockObject);
return false;
}
}
// -- so we got the lock - we "should" be certain threadSyncCount == 0
// --- but well, it is NOT the case, following DOES occur!!!!
if (threadSyncCount != 0) {
throw new Exception("DB lock error: freed acces more than it was locked - Lock counter is " + threadSyncCount);
}
Interlocked.Increment(ref threadSyncCount);
if (lockWasTaken) Monitor.Exit(_lockObject);
return true;
}
// --- Method to release the DB locks!
public static void FreeDBAccess() {
// local starts here => locks the DB access (prevents multiple concurrent DB access)!!
if (threadSyncCount != 1) {
// --- this actually happens => sometimes threadSyncCount == 2 here!!!
throw new Exception("DB lock error: freed acces more than it was locked - Lock counter is " + threadSyncCount);
}
Interlocked.Decrement(ref threadSyncCount);
}
}
public async Task DBSync() {
// ---- wait for the lock
if (!await DBLocker.WaitForDBAccess(false)) {
return;
}
// ----
await doSomeDBLoading();
// ---
DBLocker.FreeDBAccess();
}
在打开数据库事务之前,从数据库访问方法调用Lock方法
方法调用如下所示:
public static class DBLocker {
static Random rnd = new Random();
static object _lockObject = new object();
static int threadSyncCount = 0;
public static bool WaitForDBAccess(bool forceWait = false) {
bool lockWasTaken = false;
TimeSpan tSpan = new TimeSpan(0, 0, 0, 500);
int delayCount = 0;
// --- make sure we get the lock
while (!lockWasTaken) {
if (threadSyncCount == 0) {
Monitor.TryEnter(_lockObject, tSpan, ref lockWasTaken);
}
else {
await Task.Delay(rnd.Next(50, 150));
}
++delayCount;
// --- sometimes it's just OK to leave without getting the lock => just skip waiting,
// the caller method will just return without any DB access in this case!
if (!forceWait && delayCount > 5) {
// --- NB: this actually never happens!
if (lockWasTaken) Monitor.Exit(_lockObject);
return false;
}
}
// -- so we got the lock - we "should" be certain threadSyncCount == 0
// --- but well, it is NOT the case, following DOES occur!!!!
if (threadSyncCount != 0) {
throw new Exception("DB lock error: freed acces more than it was locked - Lock counter is " + threadSyncCount);
}
Interlocked.Increment(ref threadSyncCount);
if (lockWasTaken) Monitor.Exit(_lockObject);
return true;
}
// --- Method to release the DB locks!
public static void FreeDBAccess() {
// local starts here => locks the DB access (prevents multiple concurrent DB access)!!
if (threadSyncCount != 1) {
// --- this actually happens => sometimes threadSyncCount == 2 here!!!
throw new Exception("DB lock error: freed acces more than it was locked - Lock counter is " + threadSyncCount);
}
Interlocked.Decrement(ref threadSyncCount);
}
}
public async Task DBSync() {
// ---- wait for the lock
if (!await DBLocker.WaitForDBAccess(false)) {
return;
}
// ----
await doSomeDBLoading();
// ---
DBLocker.FreeDBAccess();
}
为什么上面的代码会受到竞争条件bug的影响?关于如何解决该问题有什么建议吗?您的
WaitForDBAccess
方法接受并释放互斥锁,而FreeDBAccess
不会释放互斥锁,因此在执行DB访问期间不会保留互斥锁
即使解决了这个问题,还有另一个问题:互斥体是“递归的”
互斥体是一种预同步原语,它只关心线程的互斥。因此,如果输入了互斥锁,并且当前线程已经拥有它,那么就授予了锁。这意味着“同一线程”不再意味着“递归”
正确的处理方法是使用一个异步的同步原语(或者至少一个不支持“递归”锁)。有一个内置的:SemaphoreSlim
。例如:
public static class DBLocker
{
static SemaphoreSlim _lockObject = new SemaphoreSlim(1);
static int threadSyncCount = 0;
public static async Task<bool> WaitForDBAccessAsync(bool forceWait = false)
{
TimeSpan tSpan = forceWait ? Timeout.InfiniteTimeSpan : TimeSpan.FromSeconds(5 * 500 + 0.5);
bool lockWasTaken = await _lockObject.WaitAsync(tSpan);
if (!lockWasTaken)
return false;
if (threadSyncCount != 0)
throw new Exception("DB lock error: freed acces more than it was locked - Lock counter is " + threadSyncCount);
Interlocked.Increment(ref threadSyncCount);
return true;
}
public static void FreeDBAccess()
{
if (threadSyncCount != 1)
throw new Exception("DB lock error: freed acces more than it was locked - Lock counter is " + threadSyncCount);
Interlocked.Decrement(ref threadSyncCount);
_lockObject.Release();
}
}
公共静态类DBLocker
{
静态信号量lim _lockObject=新信号量lim(1);
静态int-threadSyncCount=0;
公共静态异步任务WaitForDBAccessAsync(bool forceWait=false)
{
TimeSpan tSpan=forceWait?Timeout.InfiniteTimeSpan:TimeSpan.FromSeconds(5*500+0.5);
bool lockwastake=wait_lockObject.WaitAsync(tSpan);
如果(!锁被拿走)
返回false;
如果(threadSyncCount!=0)
抛出新异常(“DB lock error:释放的访问数大于锁定的访问数-锁定计数器为“+threadSyncCount”);
联锁。增量(参考threadSyncCount);
返回true;
}
公共静态void freedAccess()
{
如果(threadSyncCount!=1)
抛出新异常(“DB lock error:释放的访问数大于锁定的访问数-锁定计数器为“+threadSyncCount”);
联锁。减量(参考threadSyncCount);
_lockObject.Release();
}
}
谢谢你的回答。您的代码更简单、更清晰-太棒了!不过有一件事——等待2500.5s似乎有点奇怪——我猜应该是TimeSpan.fromMillimes(2500)?
左右?另一个问题:线程都是在几毫秒内启动的-有没有办法用上面的锁在时间上均匀地“分配”它们的执行?@neggenbe:TimeSpan使用您发布的代码中的值:500秒重试5次,重试之间的平均延迟为100毫秒。使用对应用程序有意义的任何值。我不知道你所说的“分配”是什么意思;等待db的所有线程都将(异步)等待,直到它可用或超时发生。哦,我的坏-将我的代码更正为TimeSpan。从毫秒(2500)
开始。现在锁似乎很完美。然而,出于某种原因,我试图弄明白,我得到了一个由2个线程组成的互锁:1。线程A接受DB访问并锁定它-很好2。线程A等待DB连接打开:await connection.OpenAsync()代码>3。线程B在A完成4之前请求DB访问。现在两个线程是互锁的——A没有打开连接,B也挂起,因为它在等待A完成。。。。知道为什么吗?好的,事实上我正在从属性设置器执行异步方法:Task=Task.Run(async()=>await\u paramsCache.InsertOrUpdate(p))
然后检索结果:bool isSaved=task.result代码>-我是否必须同时执行同步和异步方法以防止线程锁定,或者是否有一种方法可以调用异步方法而不冒线程锁定的风险???@neggenbe:do sync over async(.Result
)是一个单独的问题,也是一种反模式。通常你可以。