Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/multithreading/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Multithreading 锁定异步方法中的竞速条件_Multithreading_Thread Safety_.net 5 - Fatal编程技术网

Multithreading 锁定异步方法中的竞速条件

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();

我正在创建一个Blazor服务器应用程序,它在加载控件时执行一些
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
)是一个单独的问题,也是一种反模式。通常你可以。