c# - IIS 和 C# 线程锁
问题描述
我对使用 C# 的线程和 IIS 有疑问,我将不胜感激。
我不是一个非常熟练的 C# 程序员,所以请客气,我会描述问题,然后我会展示我的代码,最后我会问两个问题。
问题描述
我有一个 web api 方法,需要在每次请求时访问磁盘上的同一个文件夹,但是如果已经有另一个线程访问提到的文件夹,则每次调用都不应该访问该文件夹,换句话说,一次只有一个请求可以访问文件夹。
代码片段原始版本(使用监视器)
我使用实体类“banana”(:P)举了一个我的问题的例子,每个请求都包含一个香蕉的 id,这个 id 用于验证是否有另一个请求已经在访问该文件夹。
using System.Web.Hosting;
using System.Net.Http;
using System.Text;
using System.Threading;
[RoutePrefix("api/banana")]
public class BananaController
{
private static readonly object bananasLocksAccess = new object();
private static readonly Dictionary<long, object> bananasLocks = new Dictionary<long, object>();
[Route("AccessBananaFolder")]
[TokenAuthorize]
[HttpGet]
public BananaResponse AccessFolderWithBananaId(long bananaId)
{
BananaResponse r = new BananaResponse();
Boolean bananaLockTaken = false;
object bananaLocker = LockBanana(bananaId);
try
{
System.Threading.Monitor.TryEnter(bananaLocker, ref bananaLockTaken);
if (bananaLockTaken)
{
// safe code to access the folder of bananas
// ...
r = new BananaResponse("OK ACCESSING THE FOLDER");
}
else
{
r = new BananaResponse("COULD NOT ACCESS THE FOLDER RIGTH NOW, TRY AGAIN LATER");
}
}
catch (Exception ex)
{
r = new BananaResponse("AN ERROR OCURRED ACCESSING THE FOLDER OF BANANAS");
}
finally
{
if (bananaLockTaken)
{
System.Threading.Monitor.Exit(bananaLocker);
}
}
return r;
}
private static object LockBanana(long bananaId)
{
object bananaLocker;
lock (bananasLocksAccess) {
if (bananasLocks.ContainsKey(bananaId))
{
bananaLocker = bananasLocks[bananaId];
}
else
{
bananaLocker = new object();
bananasLocks[bananaId] = bananaLocker;
}
}
return bananaLocker;
}
}
我的问题是
1)这是解决C#问题的正确方法吗?
2)由于IIS使用线程池管理请求/调用(IISExpress单线程吗?),两个不同的请求访问文件夹是否有任何变化,因为由池中的同一线程管理?
非常感谢您!
==================================================== =========
更新!
代码片段版本 2(使用互斥锁)
好的,正如@STW在他的回复中建议的那样,我做了一个新版本的代码,但这次我使用互斥锁来限制对 Set 的访问,这个 set 包含当前访问香蕉文件夹的所有香蕉 id(记住这是每个香蕉 id 的一个临时文件夹),这个新版本还改进了@Remus Rusanu在主要问题的答复中提到的清理问题。
[RoutePrefix("api/banana")]
public class BananaController
{
private static readonly Mutex bananasLocksAccess = new Mutex(false, "29c447fe-d146-4ea7-ac16-bb8e453ee065");
private static readonly HashSet<long> bananasLocks = new HashSet<long>();
[Route("AccessBananaFolder")]
[TokenAuthorize]
[HttpGet]
public BananaResponse AccessFolderWithBananaId(long bananaId)
{
BananaResponse r = new BananaResponse();
bool bananaLockTaken = false;
try
{
bananaLockTaken = LockBanana(bananaId);
if (bananaLockTaken)
{
// safe code to access the folder of bananas
// ...
r = new BananaResponse("OK ACCESSING THE FOLDER");
}
else
{
r = new BananaResponse("COULD NOT ACCESS THE FOLDER RIGTH NOW, TRY AGAIN LATER");
}
}
catch (Exception ex)
{
r = new BananaResponse("AN ERROR OCURRED ACCESSING THE FOLDER OF BANANAS");
}
finally
{
if (bananaLockTaken)
{
UnlockBanana(bananaId);
}
}
return r;
}
private static bool LockBanana(long bananaId)
{
bool lockTaken = false;
try
{
lockTaken = bananasLocksAccess.WaitOne(0);
if (lockTaken)
{
lockTaken = !bananasLocks.Contains(bananaId);
if (lockTaken)
{
bananasLocks.Add(bananaId);
lockTaken = true;
}
}
bananasLocksAccess.ReleaseMutex();
}
catch (Exception e)
{
// could not complete the lock, the user should try again later
}
return lockTaken;
}
private static void UnlockBanana(long bananaId)
{
try
{
bool lockTaken = bananasLocksAccess.WaitOne();
if (lockTaken)
{
bananasLocks.Remove(bananaId);
}
bananasLocksAccess.ReleaseMutex();
}
catch (Exception e)
{
// the lock was released already
}
}
}
解决方案
由于您的代码在 IIS 中执行,因此您的代码可能有多个实例跨多个进程运行。跨进程同步需要操作系统级别的锁,而System.Threading.Mutex类就是专门为此设计的。
Mutex
有两种模式——本地和命名。要跨进程工作,您需要使用命名模式,对于名称,我建议对随机生成的 GUID 进行硬编码。
推荐阅读
- java - scala计算读取文件中包含的列表中的字符串
- c - while 循环的链表问题,我应该使用 continue 吗?
- javascript - 当另一个 div 为空时隐藏一个 div
- java - 并行化比非并行化任务花费更长的时间
- php - 如何在 Laravel 中处理全局模板变量
- nativescript - 如何从 Mapbox GL Native GeoJSONSource 获取几何图形?
- java - 将现有的 sqlite db 文件从资产复制到 android 项目时遇到问题
- regex - 正则表达式 URL 提取斜杠
- java - 使用 Spring Boot 访问两个数据源时出错
- azure-active-directory - 办公室插件 Office.context.auth.getAccessTokenAsync 错误 13004