[ C# 开发随笔 ] 在 Async/Await 情况使用 ReaderWriterLockSlim 出现无法解锁的状

在 async/await 满天飞的.net core or .net 6 的专案,前阵子有人问到一个问题,她在锁定同一时间只能一个人上传档案的时候,ReaderWriterLockSlim 无法解锁。

在解锁的时候会跳错出错误[The write lock is being released without being held.] 这是什么原因呢?请让我们继续看下去...

发生错误的程式码

首先我们先上一段 Code ,这是一个 .net 6 的上传档案的API,做的事情都很单纯,锁定执行序然后写入档案,就回传成功!

private static ReaderWriterLockSlim _readerWriterLockSlim = new ReaderWriterLockSlim();        [HttpPost][Route("Upload")]public async Task<IActionResult> UploadFile(IFormFile file){    try    {        // 锁定        if (!_readerWriterLockSlim.TryEnterWriteLock(50))        {            throw new Exception("Be Locked");        }        try        {            // 储存上传档案            var filePath = $"{Directory.GetCurrentDirectory()}/File/";            if (!Directory.Exists(filePath))            {                Directory.CreateDirectory(filePath);            }            var path = $"{filePath}{file.Name}{DateTime.Now:yyyyMMddHHmmssfff}";            await using (Stream stream = new FileStream(path, FileMode.Create))            {                // 重点问题在这行                await file.CopyToAsync(stream);            }            return Ok("Success");        }        finally        {            // 会出错的地方            _readerWriterLockSlim.ExitWriteLock();        }    }    catch (Exception e)    {        Console.WriteLine(e);        throw;    }}

这段程式执行后会收到一个 Exception : [The write lock is being released without being held.] ,根据说明是这个锁已经被解掉,但其实没有解锁,当你在上传第二个档案的时候,会得到被锁定中的结果。

错误发生的原因:

会发生这件事情的主要原因是出在 [await file.CopyToAsync(stream);] 这行,你进来的执行序,执行到这边的时候会把任务交给 IO Thread,原执行序会释放掉,当 IO Thread完成的他的任务,会交由空着的执行序接手,通常不会是原本的那条执行序,因此我们可以得到一个结论,当 await 离开原执行序后回来就会换了一条新的执行序,更换执行序这件事情我们先称之为「Thread-affine」。

但这会对我们造成什么影响呢?在跟执行序无关的程式都不会有任何影响,只是执行序的ID改变,但 ReaderWriterLockSlim 的 TryEnterWriteLock 与 ExitWriteLock 是会根据执行序作判断的,当你换了一条执行序回来之后,Exit 会判断这条执行序没有相应的 Lock,所以无法被释放,但你原先执行序的锁还在,于是导致没有人可以进来的窘境。

解决方式:

使用 AsyncReaderWriterLock 需安装 Nuget 套件 Nito.AsyncEx ,程式码如下:

private static AsyncReaderWriterLock _asyncReaderWriterLock = new AsyncReaderWriterLock();[HttpPost][Route("Upload")]public async Task<IActionResult> UploadFileV2(IFormFile file){    try    {        using (var writerLockAsync = await _asyncReaderWriterLock.WriterLockAsync())        {            var filePath = $"{Directory.GetCurrentDirectory()}/File/";            if (!Directory.Exists(filePath))            {                Directory.CreateDirectory(filePath);            }            Stream stream =                new FileStream($"{filePath}{file.Name}{DateTime.Now:yyyyMMddHHmmssfff}",                    FileMode.Create);            var currentProcessorId = Thread.GetCurrentProcessorId();            await file.CopyToAsync(stream);            var currentProcessorIad = Thread.GetCurrentProcessorId();            stream.Close();        }        return Ok("Success");     }    catch (Exception e)    {        Console.WriteLine(e);        throw;    }}

特别注意有很多执行序 Lock 都会遇到这个问题,使用Lock的时候还要多注意。

参考:

https://devblogs.microsoft.com/pfxteam/building-async-coordination-primitives-part-7-asyncreaderwriterlock/
https://github.com/StephenCleary/AsyncEx


关于作者: 网站小编

码农网专注IT技术教程资源分享平台,学习资源下载网站,58码农网包含计算机技术、网站程序源码下载、编程技术论坛、互联网资源下载等产品服务,提供原创、优质、完整内容的专业码农交流分享平台。

热门文章