C# 在读写或替换期间锁定文本文件

C# 在读写或替换期间锁定文本文件,c#,.net,file,C#,.net,File,我有一个应用程序,需要在其中创建文件,文件名中包含一个唯一的序列号。我的第一个想法是使用(因为这个应用程序没有任何其他数据存储)一个包含数字的文本文件,我会增加这个数字,这样我的应用程序将始终创建一个具有唯一id的文件。 然后我想,当有多个用户同时提交此应用程序时,可能有一个进程在前一个进程编写txt文件之前读取该文件。因此,我正在寻找一种在同一“进程”中读取和写入文件的方法(使用try-catch,这样我就可以知道另一个进程何时正在使用该文件,然后等待并尝试从中读取其他几次),而不必在其间解锁

我有一个应用程序,需要在其中创建文件,文件名中包含一个唯一的序列号。我的第一个想法是使用(因为这个应用程序没有任何其他数据存储)一个包含数字的文本文件,我会增加这个数字,这样我的应用程序将始终创建一个具有唯一id的文件。 然后我想,当有多个用户同时提交此应用程序时,可能有一个进程在前一个进程编写txt文件之前读取该文件。因此,我正在寻找一种在同一“进程”中读取和写入文件的方法(使用try-catch,这样我就可以知道另一个进程何时正在使用该文件,然后等待并尝试从中读取其他几次),而不必在其间解锁该文件。 如果我上面所说的听起来是一个糟糕的选择,你能给我一个替代方案吗?那么,对于像我的案例这样的应用程序,您将如何跟踪唯一的标识号


谢谢。

编辑:正如Alireza在评论中指出的,这不是在多个应用程序之间锁定的有效方法

您可以始终
锁定
对文件的访问权限(因此不需要依赖异常)。 e、 g:

Edit2:似乎可以使用
互斥
执行应用程序间信令

private static System.Threading.Mutex m = new System.Threading.Mutex(false, "LockMutex");

void AccessMethod()
{
    try
    {
        m.WaitOne();
        // Access the file

    }
    finally
    {
        m.ReleaseMutex();
    }
}

但这不是生成唯一ID的最佳模式。也许数据库中的序列会更好?如果您没有数据库,可以使用
Guid
s或本地数据库(我认为即使是Access也会更好)

我更喜欢使用全局互斥的复杂通用解决方案。它使用一个名称前缀为“Global\”的互斥体,这使它成为系统范围内的互斥体,即在所有进程中共享一个互斥体实例。若您的程序在友好的环境中运行,或者您可以指定严格的权限限制到您可以信任的用户帐户,那个么它工作得很好

请记住,此解决方案不是事务性的,不受线程中止/进程终止的保护

非事务性意味着,如果进程/线程被捕获在存储文件修改的中间,并终止/中止,则存储文件将处于未知状态。例如,它可以保留为空。通过先写入新值,保存文件,然后删除以前的值,可以防止数据丢失(上次使用的索引丢失)。读取过程应该期望一个包含多个数字的文件,并且应该使用最大的值

未针对线程中止进行保护意味着,如果获得互斥的线程意外中止和/或您没有适当的异常处理,则互斥可以在创建该线程的进程的生命周期内保持锁定。为了使解决方案中止得到保护,您必须在获得锁时执行超时,即更换以下永远等待的行

blnResult = iLock.Mutex.WaitOne();
有超时的东西

综上所述,我试图说,如果您正在寻找一个真正强大的解决方案,您将使用某种事务性数据库,或者自己编写一种这样的数据库:)

这是没有超时处理的工作代码(我的解决方案中不需要它)。首先,它足够健壮

using System;
using System.IO;
using System.Security.AccessControl;
using System.Security.Principal;
using System.Threading;

namespace ConsoleApplication31
{
    class Program
    {
        //You only need one instance of that Mutex for each application domain (commonly each process).
        private static SMutex mclsIOLock;

        static void Main(string[] args)
        {
            //Initialize the mutex. Here you need to know the path to the file you use to store application data.
            string strEnumStorageFilePath = Path.Combine(
                Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
                "MyAppEnumStorage.txt");
            mclsIOLock = IOMutexGet(strEnumStorageFilePath);
        }

        //Template for the main processing routine.
        public static void RequestProcess()
        {
            //This flag is used to protect against unwanted lock releases in case of recursive routines.
            bool blnLockIsSet = false;

            try
            {
                //Obtain the lock.
                blnLockIsSet = IOLockSet(mclsIOLock);

                //Read file data, update file data. Do not put much of long-running code here.
                //Other processes may be waiting for the lock release.
            }
            finally
            {
                //Release the lock if it was obtained in this particular call stack frame.
                IOLockRelease(mclsIOLock, blnLockIsSet);
            }

            //Put your long-running code here.
        }

        private static SMutex IOMutexGet(string iMutexNameBase)
        {
            SMutex clsResult = null;
            clsResult = new SMutex();

            string strSystemObjectName = @"Global\" + iMutexNameBase.Replace('\\', '_');

            //Give permissions to all authenticated users.
            SecurityIdentifier clsAuthenticatedUsers = new SecurityIdentifier(WellKnownSidType.AuthenticatedUserSid, null);
            MutexSecurity clsMutexSecurity = new MutexSecurity();
            MutexAccessRule clsMutexAccessRule = new MutexAccessRule(
                clsAuthenticatedUsers,
                MutexRights.FullControl,
                AccessControlType.Allow);
            clsMutexSecurity.AddAccessRule(clsMutexAccessRule);

            //Create the mutex or open an existing one.
            bool blnCreatedNew;
            clsResult.Mutex = new Mutex(
                false,
                strSystemObjectName,
                out blnCreatedNew,
                clsMutexSecurity);
            clsResult.IsMutexHeldByCurrentAppDomain = false;

            return clsResult;
        }

        //Release IO lock.
        private static void IOLockRelease(
            SMutex iLock,
            bool? iLockIsSetInCurrentStackFrame = null)
        {
            if (iLock != null)
            {
                lock (iLock)
                {
                    if (iLock.IsMutexHeldByCurrentAppDomain &&
                        (!iLockIsSetInCurrentStackFrame.HasValue ||
                        iLockIsSetInCurrentStackFrame.Value))
                    {
                        iLock.MutexOwnerThread = null;
                        iLock.IsMutexHeldByCurrentAppDomain = false;
                        iLock.Mutex.ReleaseMutex();
                    }
                }
            }
        }

        //Set the IO lock.
        private static bool IOLockSet(SMutex iLock)
        {
            bool blnResult = false;

            try
            {
                if (iLock != null)
                {
                    if (iLock.MutexOwnerThread != Thread.CurrentThread)
                    {
                        blnResult = iLock.Mutex.WaitOne();
                        iLock.IsMutexHeldByCurrentAppDomain = blnResult;

                        if (blnResult)
                        {
                            iLock.MutexOwnerThread = Thread.CurrentThread;
                        }
                        else
                        {
                            throw new ApplicationException("Failed to obtain the IO lock.");
                        }
                    }
                }
            }
            catch (AbandonedMutexException iMutexAbandonedException)
            {
                blnResult = true;
                iLock.IsMutexHeldByCurrentAppDomain = true;
                iLock.MutexOwnerThread = Thread.CurrentThread;
            }

            return blnResult;
        }
    }

    internal class SMutex
    {
        public Mutex Mutex;
        public bool IsMutexHeldByCurrentAppDomain;
        public Thread MutexOwnerThread;
    }
}

如果是单个应用程序,则可以在应用程序设置中存储当前号码。在启动时加载该数字。然后,对于每个请求,您可以安全地增加它并使用结果。程序关闭时保存序列号。例如:

private int _fileNumber;

// at application startup
_fileNumber = LoadFileNumberFromSettings();

// to increment
public int GetNextFile()
{
    return Interlocked.Increment(ref _fileNumber);
}

// at application shutdown
SaveFileNumberToSettings(_fileNumber);
或者,您可能希望确保文件号每次递增时都会保存。如果是,请更改
GetNextFile
方法:

private readonly object _fileLock = new object();
public int GetNextFile()
{
    lock (_fileLock)
    {
        int result = ++_fileNumber;
        SaveFileNumbertoSettings(_fileNumber);
        return result;
    }
}

还请注意,使用注册表而不是文件可能是合理的。

您想过SQlite或类似的持久层吗?你不需要重新发明轮子!这可能是一个选择,但我不想增加任何文件。我假设SQLLite将存储所有以前的ID。我的文本文件将只包含最新的id。我刚刚想:我可以使用你的想法,有两个字段,一个告诉我它是否被锁定,另一个带有数字。我将首先通过将字段更新为“true”来“锁定”,然后运行“读/写”事务,然后将“锁定”字段设置为false。我会试试的。你不需要增加文件大小!使用任何原子功能增加单个计数。这是处理多个并发请求的单个程序,还是同时运行的多个程序?
lock
的作用域是一个
应用程序空间
。如果多个并行进程试图访问该文件,
lock
无法按预期工作。哦,好吧,你教了我一些东西。问题是有很多进程实例,而不是一个。您的解决方案仅适用于单进程单线程模型。@zverev.eugene:OP对“进程”一词的使用模棱两可。在上下文中,听起来好像只有一个流程实例(应用程序),他使用术语“流程”来表示对该应用程序的单个请求。当然,我可能看错了。在任何情况下,我的答案都是基于这样一个假设,即有一个进程可以为多个并发请求提供服务。它可以轻松地处理多个线程。即使有一个进程有多个进程,您的解决方案也显示了实例变量(非静态)的使用,该变量将与定义它的类的每个实例重复。如果重新定义变量并使其成为静态变量,那么在许多服务器应用程序(如ASP.NET或WCF)的上下文中,您的解决方案仍然是错误的,因为在单个进程中有多个应用程序域,甚至静态变量也将被复制。考虑到所有这些,我不建议在没有全局系统范围同步机制的情况下使用“代码内”数据存储。
private readonly object _fileLock = new object();
public int GetNextFile()
{
    lock (_fileLock)
    {
        int result = ++_fileNumber;
        SaveFileNumbertoSettings(_fileNumber);
        return result;
    }
}