当文件被锁定时如何避免崩溃?

4

评论结果:
应用程序崩溃是由另一个问题引起的

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

我的问题是,即使将阅读器封装在try/catch中,它仍会在using行处崩溃应用程序并出现IOException(写入程序不会发生这种情况)。

我还将catch设置为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)。因此,基本上我一直在处理异常,直到文件可用为止,但这并没有起作用。

1
我只能想到两种可能导致你得到那个异常的方式:要么你在 catch 语句块中使用了 throw(不确定你是没有把代码放在 catch 中还是确实为空),要么该文件被多个位置访问,因此这不是实际引发异常的代码。现有的 catch 一定可以捕获 IOException - Joachim Isaksson
1
只是一个傻傻的双重检查,确定真的不是日志文件被锁定了吗? - Joachim Isaksson
@Prix,你应该更改问题的标题,因为它真的很误导。 - Eugeniu Torica
@Jenea,如果你觉得有些地方可以写得更好,你已经有足够的声望来自己编辑它了。 - Prix
@Prix Oh - 谢谢。我没有注意到我有这个权限。 - Eugeniu Torica
显示剩余19条评论
6个回答

5
这是我们用于此目的的代码。
/// <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...");
    }
);

1
很遗憾,IOException需要使用私有反射来解释。这似乎是唯一的编程方式来找出发生了什么。糟糕。 - usr
+1 虽然这不完全是我所需要的,但它向我展示了一些有趣的想法,非常感谢。 - Prix
为什么要使用反射来获取 HResult 的值,而它只是 IOException 的普通属性呢? - t3chb0t
@t3chb0t:这不是普通的,它是受保护的 - Timwi
1
@Timwi 哦,那他们一定改变了,因为我在这里查看(https://msdn.microsoft.com/en-us/library/system.exception.hresult(v=vs.110).aspx),在.NET 4.5中它是公共的;-) 我不知道它以前不是这样的。 - t3chb0t

2

1
您可以通过以下方式编写函数来检查文件锁定:

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

1
问题在于该函数并不是很有用,它可以返回false,但在Close()和调用者检查布尔值之间,其他进程可能已经打开了它以进行写入。 - Joachim Isaksson
我并没有看到使用这种方式和使用 using 关键字有什么不同,我的意思是如果文件可用,那么使用 using 只会打开文件,对吧?如果文件不可用,它会抛出异常,因此我避免了 close/finally 的需要。 - Prix
FileNotFoundException 是怎么样的情况?它继承自 IOException,但意思完全不同。 - gimbar

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

我并不想寻找一个变通方法,我想知道为什么这个方法不能正常工作。 - Prix
我回答了你的问题 - “忽略文件被锁定的正确方法是什么,如何保持尝试直到文件可用?” - Daniil Grankin
这是因为你忽略了我发布代码的原因,当然我发布代码的原因是为了找出问题是否在其中,并确定如何根据我的示例正确地解决它 ;) - Prix

0

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

-1
使用读写锁
正确的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 ++中并非如此)。您试过了吗? - Johan Lundberg
1
catchcatch(Exception e) 是等价的,唯一的区别在于后者可以引用 Exception。请参见相关答案 - Lukazoid

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接