在读取或写入期间锁定文本文件或替代

时间:2013-10-15 18:54:03

标签: c# .net file

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

感谢。

3 个答案:

答案 0 :(得分:1)

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

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;
    }
}

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

答案 1 :(得分:0)

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

您始终可以lock访问该文件(因此您无需依赖异常)。 e.g:

<击>
// Create a lock in your class
private static object LockObject = new object();

// and then lock on this object when you access the file like this:
lock(LockObject)
{
    ... access to the file
}

<击>

Edit2:您似乎可以使用Mutex来执行应用程序间信令。

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或本地数据库(我认为甚至可以访问Access)

答案 2 :(得分:0)

我更喜欢使用全局互斥锁的复杂通用解决方案。它使用名称前缀为“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;
    }
}