发出检查文件是否正在使用的问题

时间:2013-12-03 13:46:45

标签: c# .net windows file file-io

我有一些修改文件的线程,我试图在这之前检查文件是否正在使用 - 以防止竞争条件。

我正在使用这种方法:

protected virtual bool IsFileLocked(FileInfo file)
{
    FileStream stream = null;

    try
    {
        stream = file.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.None);
    }
    catch (IOException)
    {
        //the file is unavailable because it is:
        //still being written to
        //or being processed by another thread
        //or does not exist (has already been processed)
        return true;
    }
    finally
    {
        if (stream != null)
            stream.Close();
    }

    //file is not locked
    return false;
}

并且这样称呼它:

while (flag)
{
    if (IsFileLocked(new FileInfo(logPath)))
    {
       File.AppendAllText(logPath, Environment.NewLine + "Test");
       flag = false;
    }
}

问题是一个进程设法写入文件,但如果另一个进程同时写入,则不写入

5 个答案:

答案 0 :(得分:1)

使用文件锁定并不容易,文件流允许您锁定文件的字节范围,这是过度编程以附加文本以进行记录。

在同一进程中同步写入。

private static object lockObject = new object();
public static void AppendToFile(string fileName, string text){
   lock(lockObject){
      File.AppendAllText(fileName, text);
   }
}

要在多个进程中同步写入,您必须使用Mutex来避免竞争条件。

public static void AppendToFile(string fileName, string text){

    var gl = new GlobalNamedLock(fileName);
    if(!gl.Lock()){
         // should not happen
         // but still throw the exception some how to notice
         throw new InvalidOperationException("Could not acquire lock");
    }

    try{
        File.AppendAllText(fileName, text);
    }finally{
        gl.Unlock();
    }

}


public class GlobalNamedLock
{
    private Mutex mtx;

    public GlobalNamedLock(string strLockName)
    {
        //Name must be provided!
        if (string.IsNullOrWhiteSpace(strLockName))
        {
            //Use default name
            strLockName = ((GuidAttribute)Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(GuidAttribute), false).GetValue(0)).Value.ToString();
        }

        //Create security permissions for everyone
        //It is needed in case the mutex is used by a process with
        //different set of privileges than the one that created it
        //Setting it will avoid access_denied errors.
        MutexSecurity mSec = new MutexSecurity();
        mSec.AddAccessRule(new MutexAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null),
            MutexRights.FullControl, AccessControlType.Allow));

        //Create the global mutex
        bool bCreatedNew;
        mtx = new Mutex(false, @"Global\" + strLockName, out bCreatedNew, mSec);
    }

    public bool Lock()
    {
        return mtx.WaitOne();
    }

    public void Unlock()
    {
        //Release it
        mtx.ReleaseMutex();
    }
}

答案 1 :(得分:1)

您应该将执行实际写入的代码放入单独的方法并同步,例如:

private object WRITELOCK = new object();

private void AppendToFile(string fileName, string textToAppend)
{
    lock (WRITELOCK)
    {
        File.AppendAllText(fileName, textToAppend);
    }
}

然后所有线程都会调用AppendToFile。请注意,您仍然需要捕获线程中的异常并重试,但这样一来,该文件一次只能由一个线程写入。

线程代码应如下所示:

try
{
    AppendToFile(fileName, "Hello");
}
catch (...)
{
    // Handle errors writing to file
}

答案 2 :(得分:0)

你仍然有竞争条件:

while (flag)
{
    if (IsFileLocked(new FileInfo(logPath)))
    {
       // Because IsFileLocked has closed the file, 
       // another thread can open the file here before 
       // File.AppendAllText opens the file.
       File.AppendAllText(logPath, Environment.NewLine + "Test");
       flag = false;
    }
}

执行此操作的正确方法是打开文件,然后使用相同的流写入该文件,如果文件打开失败并重试,则捕获任何异常。在你的情况下,最简单的方法是摆脱你的IsFileLocked方法,并将File.AppendAllText放在try ... catch中。如果File.AppendAllText由于文件已被锁定而失败,请暂停一点,然后再次尝试File.AppendAllText。

答案 3 :(得分:0)

如评论中所述,您需要使用显示器或互斥锁,以确保您的支票确实对您有益。 Monitor版本更快,但不是过程安全(换句话说,如果用户打开程序的两个副本,它将无法保护您)。看起来如下:

try
{
   Monitor.Enter(shared_variable);
   if (IsFileLocked(myFile)) return;
   // ... process file here ...
}
finally
{
   Monitor.Exit(shared_variable);
}

在此示例中,只需将“shared_variable”声明为可在所有线程之间共享的任何对象。您甚至可以为此目的创建一个对象。使用互斥锁的替代方案看起来几乎相同但速度要慢得多:

Mutex globalWait = new Mutex();
if (globalWait.WaitOne())
{
   try
   {
      if (IsFileLocked(myFile)) return;
      // ... process file here ...
   }
   finally
   {
      globalWait.ReleaseMutex();
   }
}

一个非常重要的注意事项 - 这些“WaitOne”和“Enter”例程将无限期地等待线程变为可用。如果这些线程以任何方式相互依赖,那么最终可能会出现死锁。有许多优秀的资源来处理死锁,但它需要非常清楚!

答案 4 :(得分:0)

一般来说,CanWrite这样的样式函数很少,很少需要。想象一下这段代码:

if(CanRead(someFile))
{
    x = Read(someFile);
}
else
{
    // cannot read
}

这有问题。可读性可以在这两个调用之间切换(因此您的同步问题仍然存在)。当您只需要执行一次时,您还将有效地打开文件两次。这里更好的解决方案是从Read()中正确处理错误。

try
{
    x = Read(someFile);
}
catch(...)
{
    // cannot read.
}

更具体地说,您似乎正在编写一个跨进程工作的日志类。正如我在上面的示例中所做的那样,无论如何,都应该正确处理日志记录类中的写入错误,而不必使用CanWrite函数。

操作系统已经“同步”文件,因此您在写作时不会遇到任何竞争条件。但是当然写入可能不是原子的,所以你最终可能会在另一个进程文本的中间插入一些文本。

您希望您的日志记录类形成具有互斥的队列。进程A获取互斥锁,执行文件所需的所有操作,并将互斥锁传递给进程B,后者轮流等等。

为此,您可以创建一个命名的互斥锁,正如@Akash Kava已经显示的那样。