当前位置:  开发笔记 > 编程语言 > 正文

等到文件在.NET中解锁

如何解决《等到文件在.NET中解锁》经验,为你挑选了5个好方法。

在文件解锁并且可以读取和重命名之前,阻塞线程的最简单方法是什么?例如,.NET Framework中的某个地方是否存在WaitOnFile()?

我有一个服务,使用FileSystemWatcher查找要传输到FTP站点的文件,但文件创建事件在另一个进程写完文件之前触发.

理想的解决方案将有一个超时期限,因此线程在放弃之前不会永久挂起.

编辑:在尝试下面的一些解决方案后,我最终更改系统,以便所有文件写入Path.GetTempFileName(),然后执行File.Move()到最终位置.一旦FileSystemWatcher事件被触发,该文件就已经完成.



1> mafu..:

从Eric的回答开始,我进行了一些改进,使代码更加紧凑和可重用.希望它有用.

FileStream WaitForFile (string fullPath, FileMode mode, FileAccess access, FileShare share)
{
    for (int numTries = 0; numTries < 10; numTries++) {
        FileStream fs = null;
        try {
            fs = new FileStream (fullPath, mode, access, share);
            return fs;
        }
        catch (IOException) {
            if (fs != null) {
                fs.Dispose ();
            }
            Thread.Sleep (50);
        }
    }

    return null;
}


我来自未来,说这段代码仍然像魅力一样.谢谢.
用户哪个情况``fs`在`catch`块中不为空?如果`FileStream`构造函数抛出,则不会为变量赋值,并且`try`中没有任何其他内容可以抛出`IOException`.对我来说,似乎应该可以做`return new FileStream(...)`.
@PabloCosta正是!它无法关闭它,因为如果这样做,另一个线程可能会竞相进入并打开它,从而无法达到目的。此实现是正确的,因为它可以保持打开状态!让调用者担心这一点,在null上使用using是安全的,只需在using块内检查null。
“ FileStream fs = null;” 应该在try之外但在for内声明。然后在try中分配并使用fs。catch块应执行“ if(fs!= null)fs.Dispose();” (或仅在C#6中为fs?.Dispose())以确保正确清除了未返回的FileStream。

2> Eric Z Beard..:

这是我在一个相关问题上给出的答案:

    /// 
    /// Blocks until the file is not locked any more.
    /// 
    /// 
    bool WaitForFile(string fullPath)
    {
        int numTries = 0;
        while (true)
        {
            ++numTries;
            try
            {
                // Attempt to open the file exclusively.
                using (FileStream fs = new FileStream(fullPath,
                    FileMode.Open, FileAccess.ReadWrite, 
                    FileShare.None, 100))
                {
                    fs.ReadByte();

                    // If we got this far the file is ready
                    break;
                }
            }
            catch (Exception ex)
            {
                Log.LogWarning(
                   "WaitForFile {0} failed to get an exclusive lock: {1}", 
                    fullPath, ex.ToString());

                if (numTries > 10)
                {
                    Log.LogWarning(
                        "WaitForFile {0} giving up after 10 tries", 
                        fullPath);
                    return false;
                }

                // Wait for the lock to be released
                System.Threading.Thread.Sleep(500);
            }
        }

        Log.LogTrace("WaitForFile {0} returning true after {1} tries",
            fullPath, numTries);
        return true;
    }


馊主意!虽然这个概念是正确的,但更好的解决方案是返回FileStream而不是bool.如果在用户有机会锁定文件之前再次锁定文件 - 即使函数返回"false",他也会获得异常
我发现这个丑陋但唯一可行的解​​决方案
这是否真的适用于一般情况?如果在using()子句中打开文件,则在使用范围结束时关闭并解锁文件.如果有第二个进程使用与此相同的策略(重复重试),则在退出WaitForFile()之后,存在关于文件是否可打开的竞争条件.没有?
Fero的方法在哪里?

3> Simon Mourie..:

这是执行此操作的通用代码,与文件操作本身无关.这是一个如何使用它的示例:

WrapSharingViolations(() => File.Delete(myFile));

要么

WrapSharingViolations(() => File.Copy(mySourceFile, myDestFile));

您还可以定义重试计数以及重试之间的等待时间.

注意:不幸的是,底层的Win32错误(ERROR_SHARING_VIOLATION)没有暴露在.NET中,所以我添加了一个IsSharingViolation基于反射机制的小hack函数()来检查这一点.

    /// 
    /// Wraps sharing violations that could occur on a file IO operation.
    /// 
    /// The action to execute. May not be null.
    public static void WrapSharingViolations(WrapSharingViolationsCallback action)
    {
        WrapSharingViolations(action, null, 10, 100);
    }

    /// 
    /// Wraps sharing violations that could occur on a file IO operation.
    /// 
    /// The action to execute. May not be null.
    /// The exceptions callback. May be null.
    /// The retry count.
    /// The wait time in milliseconds.
    public static void WrapSharingViolations(WrapSharingViolationsCallback action, WrapSharingViolationsExceptionsCallback exceptionsCallback, int retryCount, int waitTime)
    {
        if (action == null)
            throw new ArgumentNullException("action");

        for (int i = 0; i < retryCount; i++)
        {
            try
            {
                action();
                return;
            }
            catch (IOException ioe)
            {
                if ((IsSharingViolation(ioe)) && (i < (retryCount - 1)))
                {
                    bool wait = true;
                    if (exceptionsCallback != null)
                    {
                        wait = exceptionsCallback(ioe, i, retryCount, waitTime);
                    }
                    if (wait)
                    {
                        System.Threading.Thread.Sleep(waitTime);
                    }
                }
                else
                {
                    throw;
                }
            }
        }
    }

    /// 
    /// Defines a sharing violation wrapper delegate.
    /// 
    public delegate void WrapSharingViolationsCallback();

    /// 
    /// Defines a sharing violation wrapper delegate for handling exception.
    /// 
    public delegate bool WrapSharingViolationsExceptionsCallback(IOException ioe, int retry, int retryCount, int waitTime);

    /// 
    /// Determines whether the specified exception is a sharing violation exception.
    /// 
    /// The exception. May not be null.
    /// 
    ///     true if the specified exception is a sharing violation exception; otherwise, false.
    /// 
    public static bool IsSharingViolation(IOException exception)
    {
        if (exception == null)
            throw new ArgumentNullException("exception");

        int hr = GetHResult(exception, 0);
        return (hr == -2147024864); // 0x80070020 ERROR_SHARING_VIOLATION

    }

    /// 
    /// Gets the HRESULT of the specified exception.
    /// 
    /// The exception to test. May not be null.
    /// The default value in case of an error.
    /// The HRESULT value.
    public static int GetHResult(IOException exception, int defaultValue)
    {
        if (exception == null)
            throw new ArgumentNullException("exception");

        try
        {
            const string name = "HResult";
            PropertyInfo pi = exception.GetType().GetProperty(name, BindingFlags.NonPublic | BindingFlags.Instance); // CLR2
            if (pi == null)
            {
                pi = exception.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance); // CLR4
            }
            if (pi != null)
                return (int)pi.GetValue(exception, null);
        }
        catch
        {
        }
        return defaultValue;
    }


Marshal.GetHRForException http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.marshal.gethrforexception.aspx
在.NET Framework 4.5,.NET Standard和.NET Core中,HResult是Exception类的公共属性.这不再需要反思.来自MSDN:`从.NET Framework 4.5开始,HResult属性的setter受到保护,而其getter是公共的.在以前版本的.NET Framework中,getter和setter都受到保护
他们真的可以提供一个`SharingViolationException`.实际上,只要它从`IOException`下降,它们仍然可以向后兼容.他们真的,真的应该.

4> 小智..:

我为这些事情拼凑了一个助手类.如果您可以控制访问该文件的所有内容,它将起作用.如果你期待其他一些事情的争论,那么这是毫无价值的.

using System;
using System.IO;
using System.Threading;

/// 
/// This is a wrapper aroung a FileStream.  While it is not a Stream itself, it can be cast to
/// one (keep in mind that this might throw an exception).
/// 
public class SafeFileStream: IDisposable
{
    #region Private Members
    private Mutex m_mutex;
    private Stream m_stream;
    private string m_path;
    private FileMode m_fileMode;
    private FileAccess m_fileAccess;
    private FileShare m_fileShare;
    #endregion//Private Members

    #region Constructors
    public SafeFileStream(string path, FileMode mode, FileAccess access, FileShare share)
    {
        m_mutex = new Mutex(false, String.Format("Global\\{0}", path.Replace('\\', '/')));
        m_path = path;
        m_fileMode = mode;
        m_fileAccess = access;
        m_fileShare = share;
    }
    #endregion//Constructors

    #region Properties
    public Stream UnderlyingStream
    {
        get
        {
            if (!IsOpen)
                throw new InvalidOperationException("The underlying stream does not exist - try opening this stream.");
            return m_stream;
        }
    }

    public bool IsOpen
    {
        get { return m_stream != null; }
    }
    #endregion//Properties

    #region Functions
    /// 
    /// Opens the stream when it is not locked.  If the file is locked, then
    /// 
    public void Open()
    {
        if (m_stream != null)
            throw new InvalidOperationException(SafeFileResources.FileOpenExceptionMessage);
        m_mutex.WaitOne();
        m_stream = File.Open(m_path, m_fileMode, m_fileAccess, m_fileShare);
    }

    public bool TryOpen(TimeSpan span)
    {
        if (m_stream != null)
            throw new InvalidOperationException(SafeFileResources.FileOpenExceptionMessage);
        if (m_mutex.WaitOne(span))
        {
            m_stream = File.Open(m_path, m_fileMode, m_fileAccess, m_fileShare);
            return true;
        }
        else
            return false;
    }

    public void Close()
    {
        if (m_stream != null)
        {
            m_stream.Close();
            m_stream = null;
            m_mutex.ReleaseMutex();
        }
    }

    public void Dispose()
    {
        Close();
        GC.SuppressFinalize(this);
    }

    public static explicit operator Stream(SafeFileStream sfs)
    {
        return sfs.UnderlyingStream;
    }
    #endregion//Functions
}

它使用命名的互斥锁.那些希望访问该文件的人试图获取对指定互斥锁的控制权,该互斥锁共享该文件的名称(将'\'变为'/').您可以使用Open(),它将停止直到可以访问互斥锁,或者您可以使用TryOpen(TimeSpan),它尝试获取给定持续时间的互斥锁,如果无法在时间跨度内获取,则返回false.这应该最有可能在使用块内部使用,以确保正确释放锁定,并且在放置此对象时将正确处理流(如果打开).

我做了一个快速的测试,大约有20件事要对文件进行各种读/写操作,并且没有看到任何损坏.显然它不是很先进,但它应该适用于大多数简单的情况.



5> jason saldo..:

对于这个特定的应用程序,直接观察文件将不可避免地导致难以追踪的错误,特别是当文件大小增加时.以下是两种不同的策略.

Ftp两个文件,但只看一个.例如,发送文件important.txt和important.finish.只监视完成文件但处理txt.

FTP一个文件,但完成后重命名.例如,发送important.wait并让发件人在完成后将其重命名为important.txt.

祝好运!

推荐阅读
手机用户2502852037
这个屌丝很懒,什么也没留下!