【问题标题】:Avoid crashing when file is locked?文件锁定时避免崩溃?
【发布时间】:2012-10-14 10:11:06
【问题描述】:

来自 cmets 的解决方案:
应用程序崩溃是由另一个问题引起的

我正在从 2 个不同的应用程序读取/写入文件,当文件被读取或写入时,它将始终被应用程序 A 或 B 锁定,并且它们都使用FileShare.None

我的问题是,即使将阅读器包裹在 try/catch 周围,它仍然会在 using 行使用 IOException 使应用程序崩溃(写入器不会发生)。

我也把它写成了catch (IOException ...,我相信除了让它更具可读性之外没有什么区别。

文件被锁定时忽略并继续尝试直到文件可用的正确方法是什么?

while (true)
{
    try
    {
        using (FileStream stream = new FileStream("test_file.dat", FileMode.Open, FileAccess.Read, FileShare.None))
        {
            using (TextReader reader = new StreamReader(stream))
            {
                // bla bla bla does not matter
            }
        }
    }
    catch
    {
        // bla bla bla does not matter again
    }
    Thread.Sleep(500);
}

private bool WriteData(string data)
{
    try
    {
        using (FileStream stream = new FileStream("test_file.dat", FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None))
        {
            stream.SetLength(0);
            using (TextWriter writer = new StreamWriter(stream))
            {
                writer.Write(data);
            }
        }
        return true;
    }
    catch
    {
        return false;
    }
}

请注意,当文件被用于读取或写入的任何进程时,我不会向任何人授予共享权限(作者和读者都使用FileShare.None)所以基本上我正在处理异常,直到文件可用这不起作用。

【问题讨论】:

  • 当你说“崩溃”时,你的意思是当你“独立”运行它时它实际上崩溃了,还是只是它显示了在调试器中抛出的异常?
  • 独立运行它会崩溃“应用程序停止运行”并且我在错误日志文件崩溃之前收到的最后一条错误消息与被锁定的文件有关:System.IO.IOException: The process cannot access the file 'long_path_of_where_the_file_was' because it is being used by another process.
  • 使用 ReaderWriterLock 类怎么样?
  • 只有两种方法我可以看到你得到这个异常;要么你在你的catch子句中使用throw(不确定你是否把代码留在catch里面还是它实际上是空的),或者文件在多个地方被访问所以这不是实际的代码抛出例外。现有的捕获肯定会捕获IOException
  • 只是一个愚蠢的仔细检查,它实际上不是锁定的日志文件?

标签: c# .net-4.0 concurrency filelock


【解决方案1】:

这是我们用于此目的的代码。

/// <summary>
/// Executes the specified action. If the action results in a file sharing violation exception, the action will be
/// repeatedly retried after a short delay (which increases after every failed attempt).
/// </summary>
/// <param name="action">The action to be attempted and possibly retried.</param>
/// <param name="maximum">Maximum amount of time to keep retrying for. When expired, any sharing violation
/// exception will propagate to the caller of this method. Use null to retry indefinitely.</param>
/// <param name="onSharingVio">Action to execute when a sharing violation does occur (is called before the waiting).</param>
public static void WaitSharingVio(Action action, TimeSpan? maximum = null, Action onSharingVio = null)
{
    WaitSharingVio<bool>(() => { action(); return true; }, maximum, onSharingVio);
}

/// <summary>
/// Executes the specified function. If the function results in a file sharing violation exception, the function will be
/// repeatedly retried after a short delay (which increases after every failed attempt).
/// </summary>
/// <param name="func">The function to be attempted and possibly retried.</param>
/// <param name="maximum">Maximum amount of time to keep retrying for. When expired, any sharing violation
/// exception will propagate to the caller of this method. Use null to retry indefinitely.</param>
/// <param name="onSharingVio">Action to execute when a sharing violation does occur (is called before the waiting).</param>
public static T WaitSharingVio<T>(Func<T> func, TimeSpan? maximum = null, Action onSharingVio = null)
{
    var started = DateTime.UtcNow;
    int sleep = 279;
    while (true)
    {
        try
        {
            return func();
        }
        catch (IOException ex)
        {
            int hResult = 0;
            try { hResult = (int) ex.GetType().GetProperty("HResult", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(ex, null); }
            catch { }
            if (hResult != -2147024864) // 0x80070020 ERROR_SHARING_VIOLATION
                throw;
            if (onSharingVio != null)
                onSharingVio();
        }

        if (maximum != null)
        {
            int leftMs = (int) (maximum.Value - (DateTime.UtcNow - started)).TotalMilliseconds;
            if (sleep > leftMs)
            {
                Thread.Sleep(leftMs);
                return func(); // or throw the sharing vio exception
            }
        }

        Thread.Sleep(sleep);
        sleep = Math.Min((sleep * 3) >> 1, 10000);
    }
}

使用示例:

Utilities.WaitSharingVio(
    action: () =>
    {
        using (var f = File.Open(file, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None))
        {
            // ... blah, process the file
        }
    },
    onSharingVio: () =>
    {
        Console.WriteLine("Sharing violation. Trying again soon...");
    }
);

【讨论】:

  • 很遗憾需要使用私有反射来解释 IOException。没有其他方法可以以编程方式找出发生了什么。太可怕了。
  • +1 虽然这不是我真正需要的,但它确实向我展示了一些有趣的想法,非常感谢。
  • HResultIOException 的普通属性时,为什么要使用反射来获取它的值?
  • @t3chb0t:不是,it’s protected
  • @Timwi 哦,那么他们一定已经改变了它,因为我在寻找 here 并且在 .NET 4.5 中它是公开的 ;-) 我不知道它并不总是这样。跨度>
【解决方案2】:

我使用来自Is there a global named reader/writer lock? 的信息做过一次。

我想结果有点像ReaderWriterLockSlim,它适用于多个进程而不是线程访问资源的情况。

【讨论】:

    【解决方案3】:

    您可以使用互斥对象来保护共享资源不被多个线程或进程同时访问。

    【讨论】:

    • 好吧,我不是在寻找解决方法,我想知道为什么该方法不能正常工作。
    • 我为你回答了问题 - “当文件被锁定并继续尝试直到文件可用时,忽略的正确方法是什么?”
    • 那是因为你忽略了我发布代码的意义,我发布代码的原因是要找出问题是否在其中,以及解决问题的正确方法是什么关于我的样品;)
    【解决方案4】:

    您可以通过编写如下函数来检查您的文件锁定:

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

    【讨论】:

    • 问题是该函数不是很有帮助,它可以返回false,但在Close()和调用者检查布尔值之间,其他进程可能已经打开它进行写入。
    • 我并没有真正看到从 using 到 this 的区别,我的意思是 using 只会在文件可用的情况下打开文件,对吗?如果不是,它会按原样抛出,因此我避免关闭/最终需要。
    • FileNotFoundException 怎么样?它继承自 IOException,意味着完全不同的东西。
    【解决方案5】:

    Timwi 的回答对我们帮助很大(尽管在另一个上下文中),但我发现如果你想从所有 IOExceptions 中获取 HResult,还需要添加标志“BindingFlags.Public”:

    public static int GetHresult(this IOException ex)
    {
       return (int)ex.GetType().GetProperty("HResult", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).GetValue(ex, null);
    }
    

    【讨论】:

      【解决方案6】:

      使用读写锁
      正确的 catch 语法

      catch(Exception e)
      


      while (true)
      {
          try
          {
              using (FileStream stream = new FileStream("test_file.dat", FileMode.Open, FileAccess.Read, FileShare.None))
              {
                  using (TextReader reader = new StreamReader(stream))
                  {
                      // bla bla bla does not matter
                  }
              }
          }
          catch(Exception e)
          {
              // bla bla bla does not matter again
          }
          Thread.Sleep(500);
      }
      

      【讨论】:

      • @Prix,我在其他地方读到,您需要像这个答案所暗示的那样实际指定一个值 e。我不清楚为什么(在 python 或 c++ 中不是这种情况)。你试过了吗?
      • catchcatch(Exception e) 是等价的,唯一的区别是Exception 可以在后一个示例中引用。见related answer
      猜你喜欢
      • 1970-01-01
      • 2021-10-03
      • 1970-01-01
      • 1970-01-01
      • 2015-11-16
      • 2010-11-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多