首页 > 解决方案 > 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;
    }
}

我的问题是

非常感谢您!

==================================================== =========

更新!

代码片段版本 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
        }
    }

}

标签: c#multithreading

解决方案


由于您的代码在 IIS 中执行,因此您的代码可能有多个实例跨多个进程运行。跨进程同步需要操作系统级别的锁,而System.Threading.Mutex类就是专门为此设计的。

Mutex有两种模式——本地命名。要跨进程工作,您需要使用命名模式,对于名称,我建议对随机生成的 GUID 进行硬编码。


推荐阅读