Сведения о вопросе

LiKIY

16:03, 1st July, 2020

Теги

c#   .net   io   filelock    

Как проверить наличие блокировки файлов?

Просмотров: 462   Ответов: 12

Есть ли способ проверить, заблокирован ли файл без использования блока try / catch?

Прямо сейчас, единственный способ, который я знаю, это просто открыть файл и поймать любой System.IO.IOException .



  Сведения об ответе

9090

18:03, 1st July, 2020

Когда я столкнулся с подобной проблемой, я закончил со следующим кодом:

public bool IsFileLocked(string filePath)
{
    try
    {
        using (File.Open(filePath, FileMode.Open)){}
    }
    catch (IOException e)
    {
        var errorCode = Marshal.GetHRForException(e) & ((1 << 16) - 1);

        return errorCode == 32 || errorCode == 33;
    }

    return false;
}


  Сведения об ответе

pumpa

18:03, 1st July, 2020

Другие ответы опираются на старую информацию. Это одно обеспечивает лучшее решение.

Давно уже невозможно было достоверно получить список процессов, блокирующих файл, потому что Windows просто не отслеживал эту информацию. Чтобы поддержать перезапуск Manager API, эта информация теперь отслеживается. Перезапуск Manager API доступен начиная с Windows Vista и Windows Server 2008 ( диспетчер перезапуска: требования времени выполнения).

Я собрал код, который принимает путь к файлу и возвращает List<Process> всех процессов, блокирующих этот файл.

static public class FileUtil
{
    [StructLayout(LayoutKind.Sequential)]
    struct RM_UNIQUE_PROCESS
    {
        public int dwProcessId;
        public System.Runtime.InteropServices.ComTypes.FILETIME ProcessStartTime;
    }

    const int RmRebootReasonNone = 0;
    const int CCH_RM_MAX_APP_NAME = 255;
    const int CCH_RM_MAX_SVC_NAME = 63;

    enum RM_APP_TYPE
    {
        RmUnknownApp = 0,
        RmMainWindow = 1,
        RmOtherWindow = 2,
        RmService = 3,
        RmExplorer = 4,
        RmConsole = 5,
        RmCritical = 1000
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    struct RM_PROCESS_INFO
    {
        public RM_UNIQUE_PROCESS Process;

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_APP_NAME + 1)]
        public string strAppName;

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_SVC_NAME + 1)]
        public string strServiceShortName;

        public RM_APP_TYPE ApplicationType;
        public uint AppStatus;
        public uint TSSessionId;
        [MarshalAs(UnmanagedType.Bool)]
        public bool bRestartable;
    }

    [DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)]
    static extern int RmRegisterResources(uint pSessionHandle,
                                          UInt32 nFiles,
                                          string[] rgsFilenames,
                                          UInt32 nApplications,
                                          [In] RM_UNIQUE_PROCESS[] rgApplications,
                                          UInt32 nServices,
                                          string[] rgsServiceNames);

    [DllImport("rstrtmgr.dll", CharSet = CharSet.Auto)]
    static extern int RmStartSession(out uint pSessionHandle, int dwSessionFlags, string strSessionKey);

    [DllImport("rstrtmgr.dll")]
    static extern int RmEndSession(uint pSessionHandle);

    [DllImport("rstrtmgr.dll")]
    static extern int RmGetList(uint dwSessionHandle,
                                out uint pnProcInfoNeeded,
                                ref uint pnProcInfo,
                                [In, Out] RM_PROCESS_INFO[] rgAffectedApps,
                                ref uint lpdwRebootReasons);

    /// <summary>
    /// Find out what process(es) have a lock on the specified file.
    /// </summary>
    /// <param name="path">Path of the file.</param>
    /// <returns>Processes locking the file</returns>
    /// <remarks>See also:
    /// http://msdn.microsoft.com/en-us/library/windows/desktop/aa373661(v=vs.85).aspx
    /// http://wyupdate.googlecode.com/svn-history/r401/trunk/frmFilesInUse.cs (no copyright in code at time of viewing)
    /// 
    /// </remarks>
    static public List<Process> WhoIsLocking(string path)
    {
        uint handle;
        string key = Guid.NewGuid().ToString();
        List<Process> processes = new List<Process>();

        int res = RmStartSession(out handle, 0, key);

        if (res != 0)
            throw new Exception("Could not begin restart session.  Unable to determine file locker.");

        try
        {
            const int ERROR_MORE_DATA = 234;
            uint pnProcInfoNeeded = 0,
                 pnProcInfo = 0,
                 lpdwRebootReasons = RmRebootReasonNone;

            string[] resources = new string[] { path }; // Just checking on one resource.

            res = RmRegisterResources(handle, (uint)resources.Length, resources, 0, null, 0, null);

            if (res != 0) 
                throw new Exception("Could not register resource.");                                    

            //Note: there's a race condition here -- the first call to RmGetList() returns
            //      the total number of process. However, when we call RmGetList() again to get
            //      the actual processes this number may have increased.
            res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, null, ref lpdwRebootReasons);

            if (res == ERROR_MORE_DATA)
            {
                // Create an array to store the process results
                RM_PROCESS_INFO[] processInfo = new RM_PROCESS_INFO[pnProcInfoNeeded];
                pnProcInfo = pnProcInfoNeeded;

                // Get the list
                res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, processInfo, ref lpdwRebootReasons);

                if (res == 0)
                {
                    processes = new List<Process>((int)pnProcInfo);

                    // Enumerate all of the results and add them to the 
                    // list to be returned
                    for (int i = 0; i < pnProcInfo; i++)
                    {
                        try
                        {
                            processes.Add(Process.GetProcessById(processInfo[i].Process.dwProcessId));
                        }
                        // catch the error -- in case the process is no longer running
                        catch (ArgumentException) { }
                    }
                }
                else
                    throw new Exception("Could not list processes locking resource.");                    
            }
            else if (res != 0)
                throw new Exception("Could not list processes locking resource. Failed to get size of result.");                    
        }
        finally
        {
            RmEndSession(handle);
        }

        return processes;
    }
}

UPDATE

Вот еще одно обсуждение с примером кода о том, как использовать Restart Manager API.


  Сведения об ответе

repe

18:03, 1st July, 2020

Нет, к сожалению, и если вы подумаете об этом, эта информация все равно будет бесполезной, так как файл может быть заблокирован в следующую секунду (читай: короткий промежуток времени).

Почему именно вам нужно знать, заблокирован ли файл в любом случае? Зная, что это может дать нам какой-то другой способ дать вам хороший совет.

Если ваш код будет выглядеть так:

if not locked then
    open and update file

Затем между двумя строками другой процесс может легко заблокировать файл, что даст вам ту же проблему, которую вы пытались избежать с самого начала: исключения.


  Сведения об ответе

P_S_S

18:03, 1st July, 2020

Вы также можете проверить, использует ли этот файл какой-либо процесс, и показать список программ, которые вы должны закрыть, чтобы продолжить работу, как это делает установщик.

public static string GetFileProcessName(string filePath)
{
    Process[] procs = Process.GetProcesses();
    string fileName = Path.GetFileName(filePath);

    foreach (Process proc in procs)
    {
        if (proc.MainWindowHandle != new IntPtr(0) && !proc.HasExited)
        {
            ProcessModule[] arr = new ProcessModule[proc.Modules.Count];

            foreach (ProcessModule pm in proc.Modules)
            {
                if (pm.ModuleName == fileName)
                    return proc.ProcessName;
            }
        }
    }

    return null;
}


  Сведения об ответе

fo_I_K

18:03, 1st July, 2020

Вместо использования interop вы можете использовать методы класса .NET FileStream Lock and Unlock:

FileStream.Lock http://msdn.microsoft.com/en-us/library/system.io.filestream.lock.aspx

FileStream.Unlock http://msdn.microsoft.com/en-us/library/system.io.filestream.unlock.aspx


  Сведения об ответе

pumpa

18:03, 1st July, 2020

Вы можете вызвать LockFile через interop в интересующем Вас регионе файла. Это не вызовет исключения, если оно будет успешно выполнено, вы будете иметь блокировку на той части файла (которая удерживается вашим процессом), эта блокировка будет удерживаться до тех пор, пока вы не вызовете UnlockFile или ваш процесс не умрет.


  Сведения об ответе

+-*/

18:03, 1st July, 2020

Вариация отличного ответа DixonD (выше).

public static bool TryOpen(string path,
                           FileMode fileMode,
                           FileAccess fileAccess,
                           FileShare fileShare,
                           TimeSpan timeout,
                           out Stream stream)
{
    var endTime = DateTime.Now + timeout;

    while (DateTime.Now < endTime)
    {
        if (TryOpen(path, fileMode, fileAccess, fileShare, out stream))
            return true;
    }

    stream = null;
    return false;
}

public static bool TryOpen(string path,
                           FileMode fileMode,
                           FileAccess fileAccess,
                           FileShare fileShare,
                           out Stream stream)
{
    try
    {
        stream = File.Open(path, fileMode, fileAccess, fileShare);
        return true;
    }
    catch (IOException e)
    {
        if (!FileIsLocked(e))
            throw;

        stream = null;
        return false;
    }
}

private const uint HRFileLocked = 0x80070020;
private const uint HRPortionOfFileLocked = 0x80070021;

private static bool FileIsLocked(IOException ioException)
{
    var errorCode = (uint)Marshal.GetHRForException(ioException);
    return errorCode == HRFileLocked || errorCode == HRPortionOfFileLocked;
}

Использование:

private void Sample(string filePath)
{
    Stream stream = null;

    try
    {
        var timeOut = TimeSpan.FromSeconds(1);

        if (!TryOpen(filePath,
                     FileMode.Open,
                     FileAccess.ReadWrite,
                     FileShare.ReadWrite,
                     timeOut,
                     out stream))
            return;

        // Use stream...
    }
    finally
    {
        if (stream != null)
            stream.Close();
    }
}


  Сведения об ответе

PAGE

18:03, 1st July, 2020

Вот вариант кода DixonD, который добавляет количество секунд, чтобы дождаться разблокировки файла, и повторите попытку:

public bool IsFileLocked(string filePath, int secondsToWait)
{
    bool isLocked = true;
    int i = 0;

    while (isLocked &&  ((i < secondsToWait) || (secondsToWait == 0)))
    {
        try
        {
            using (File.Open(filePath, FileMode.Open)) { }
            return false;
        }
        catch (IOException e)
        {
            var errorCode = Marshal.GetHRForException(e) & ((1 << 16) - 1);
            isLocked = errorCode == 32 || errorCode == 33;
            i++;

            if (secondsToWait !=0)
                new System.Threading.ManualResetEvent(false).WaitOne(1000);
        }
    }

    return isLocked;
}


if (!IsFileLocked(file, 10))
{
    ...
}
else
{
    throw new Exception(...);
}


  Сведения об ответе

nYU

18:03, 1st July, 2020

Вы можете увидеть, заблокирован ли файл, попытавшись сначала прочитать или заблокировать его самостоятельно.

Пожалуйста, смотрите мой ответ здесь для получения дополнительной информации .


  Сведения об ответе

ЯЯ__4

18:03, 1st July, 2020

Затем между двумя строками другой процесс может легко заблокировать файл, что даст вам ту же проблему, которую вы пытались избежать с самого начала: исключения.

Однако в этом случае вы будете знать, что проблема временная, и повторите попытку позже. (E.g., вы можете написать поток, который, столкнувшись с блокировкой при попытке записи, продолжает повторять попытку, пока блокировка не исчезнет.)

IOException, с другой стороны, сам по себе недостаточно специфичен, чтобы блокировка была причиной отказа IO. Могут быть причины, которые не являются временными.


  Сведения об ответе

repe

18:03, 1st July, 2020

То, что я в итоге сделал, это:

internal void LoadExternalData() {
    FileStream file;

    if (TryOpenRead("filepath/filename", 5, out file)) {
        using (file)
        using (StreamReader reader = new StreamReader(file)) {
         // do something 
        }
    }
}


internal bool TryOpenRead(string path, int timeout, out FileStream file) {
    bool isLocked = true;
    bool condition = true;

    do {
        try {
            file = File.OpenRead(path);
            return true;
        }
        catch (IOException e) {
            var errorCode = Marshal.GetHRForException(e) & ((1 << 16) - 1);
            isLocked = errorCode == 32 || errorCode == 33;
            condition = (isLocked && timeout > 0);

            if (condition) {
                // we only wait if the file is locked. If the exception is of any other type, there's no point on keep trying. just return false and null;
                timeout--;
                new System.Threading.ManualResetEvent(false).WaitOne(1000);
            }
        }
    }
    while (condition);

    file = null;
    return false;
}


  Сведения об ответе

crush

18:03, 1st July, 2020

То же самое, но в Powershell году

function Test-FileOpen
{
    Param
    ([string]$FileToOpen)
    try
    {
        $openFile =([system.io.file]::Open($FileToOpen,[system.io.filemode]::Open))
        $open =$true
        $openFile.close()
    }
    catch
    {
        $open = $false
    }
    $open
}


Ответить на вопрос

Чтобы ответить на вопрос вам нужно войти в систему или зарегистрироваться