在 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
<<: 四个可以帮助你找回 iPhone/iPad 上遗失的照片影片的实用方法
>>: 资安学习路上-网站常见漏洞与 Injection的爱恨情仇4
无状态stateless指的是web客户端在发送请求时,到底需不需要一直带着验证资讯,或者是所谓的上...
ER Diagram (Entity Relationship Model) 是一个非常热门的资料库...
好的,你很辛苦的写了很多API function,但是你却不希望闲杂人等没事就call一下你的API...
今天要来进入到生命周期的第二个环节: Updating 更新,继上篇的 Mounting 元件挂载...
大致了解网路是什麽之後,那每天逛的网页又是什麽呢? 什麽是网页? 网页是一份档案,通常会储存在服务器...