Как проверить блокировку файла? [Дубликат]

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

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

4.08.2008 14:56:57
Проблема в том, что IOException может быть вызвано по многим причинам, кроме заблокированного файла.
JohnFx 9.02.2010 16:48:48
Это старый вопрос, и все старые ответы являются неполными или неправильными. Я добавил полный и правильный ответ.
Eric J. 23.12.2013 18:27:10
Я знаю, что это не совсем ответ на вопрос, как есть, но некоторые подмножества разработчиков, которые обращаются к этому за помощью, могут иметь эту опцию: Если вы запускаете процесс, которому принадлежит блокировка с System.Diagnostics.Process, вы можете .WaitForExit ().
amalgamate 2.02.2016 17:44:34
12 ОТВЕТОВ
РЕШЕНИЕ

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

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

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

if not locked then
    open and update file

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

132
14.03.2013 22:54:07
Если файл заблокирован, мы можем подождать некоторое время и повторить попытку. Если это другой тип проблемы с доступом к файлу, мы должны просто распространить исключение.
DixonD 8.10.2010 05:11:57
Да, но автономная проверка того, заблокирован ли файл, бесполезна, единственный правильный способ сделать это - попытаться открыть файл для той цели, в которой он вам нужен, а затем решить проблему блокировки на этом этапе. А потом, как вы говорите, подождите или разберитесь с этим по-другому.
Lasse V. Karlsen 8.10.2010 08:06:29
То же самое можно сказать и о правах доступа, хотя, конечно, это будет маловероятно.
ctusch 6.06.2013 09:38:04
@ LasseV.Karlsen Еще одним преимуществом упреждающей проверки является то, что вы можете уведомить пользователя перед попыткой возможной длительной операции и прерывания на полпути. Блокировка, возникающая на полпути, конечно, все еще возможна и должна быть обработана, но во многих сценариях это значительно помогло бы пользователю.
Thiru 19.06.2013 18:01:34
Я думаю, что лучше всего сделать это метод File.ReadWaitForUnlock (file, timeout). и возвращает null или FileStream в зависимости от успеха. Я следую логике прямо здесь?
Bart Calixto 13.12.2013 20:50:08

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

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;
}
180
1.09.2013 19:32:17
@kite: теперь есть лучший способ stackoverflow.com/a/20623302/141172
Eric J. 22.01.2014 22:48:29
Что, если между return falseи вашей попыткой открыть файл снова что-то еще подхватывает его? Условия гонки ахой!
jocull 1.04.2014 20:17:49
@DanRevell Проверьте это: msdn.microsoft.com/library/windows/desktop/aa378137.aspx
DixonD 15.01.2015 18:06:29
@RenniePet Следующая страница должна быть более полезной: msdn.microsoft.com/en-us/library/windows/desktop/… Соответствующие ошибки: ERROR_SHARING_VIOLATION и ERROR_LOCK_VIOLATION
DixonD 8.06.2016 17:49:22
Какова цель маскировки битов, если сравнить результат с константой? Кроме того, GetHRForExceptionимеет побочные эффекты, HResultможно прочитать непосредственно с .NET 4.5.
BartoszKP 18.04.2017 16:51:59

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

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

Я собрал код, который берет путь к файлу и возвращает 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;
    }
}

ОБНОВИТЬ

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

143
7.02.2016 00:29:32
Единственный ответ здесь, который на самом деле отвечает на вопрос ОП ... приятно!
Serj Sagan 23.12.2013 15:14:47
Будет ли это работать, если файл находится на общем сетевом ресурсе и файл, возможно, заблокирован на другом компьютере?
Coder14 9.12.2014 15:52:20
Я просто использовал это, и он работает по сети.
Jonathan D 19.02.2015 11:03:55
Если кому-то интересно, я создал суть, вдохновленную этим ответом, но более простую и улучшенную благодаря правильно отформатированной документации из msdn. Я также черпал вдохновение из статьи Раймонда Чена и позаботился о состоянии гонки. Кстати, я заметил, что запуск этого метода занимает около 30 мс (с одним методом RmGetList, который занимает 20 мс), в то время как метод DixonD, пытающийся получить блокировку, занимает менее 5 мс ... Имейте это в виду, если вы планируете использовать его в плотная петля ...
Melvyn 12.05.2016 09:09:11
@VadimLevkovsky ой извините, вот рабочая ссылка: gist.github.com/mlaily/9423f1855bb176d52a327f5874915a97
Melvyn 16.08.2017 09:39:13

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

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;
}
19
7.02.2016 00:30:11
Это может только сказать, какой процесс удерживает исполняемый модуль (dll) заблокированным. Он не скажет вам, какой процесс заблокировал, скажем, ваш XML-файл.
Constantin 8.07.2012 20:58:21

Вместо использования взаимодействия вы можете использовать методы класса .NET FileStream Lock и 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

16
8.03.2010 17:09:15
Это действительно правильный ответ, поскольку он дает пользователю возможность не только блокировать / разблокировать файлы, но и разделы файлов. Все комментарии «Вы не можете сделать это без транзакций» могут вызывать серьезную озабоченность, но они бесполезны, поскольку они делают вид, что функциональности нет или что-то скрыто, когда ее нет.
BrainSlugs83 13.10.2011 21:49:55
На самом деле, это не решение, потому что вы не можете создать экземпляр FileStream, если файл заблокирован. (исключение будет брошено)
Zé Carlos 27.01.2012 17:54:16

Вот вариант кода 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(...);
}
9
7.02.2016 00:35:28
Ну, я делал то же самое в своем первоначальном ответе, пока кто-то не решил упростить его :) stackoverflow.com/posts/3202085/revisions
DixonD 10.01.2014 11:25:46

Вариант отличного ответа от 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();
    }
}
8
7.02.2016 00:32:39
Это пока единственное практическое решение. И это работает.
Contango 5.02.2013 19:28:38
Boooyyyyy ... Тебе лучше положить туда Thread.Sleep (200) и выйти из моего процессора!
Paul Knopf 16.04.2014 15:27:05
Какую часть вы хотите спать? Почему?
Tristan 17.04.2014 21:39:35
@ Тристан: Пол Нопф хотел использовать Thread.Sleep между попытками доступа.
DixonD 22.08.2014 05:42:04
Попробуйте прочитать комментарий @ PaulKnopf без использования голоса разгневанных подруг в вашей голове.
Phil Cooper 30.10.2015 09:15:33

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

7
24.07.2009 08:29:14

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

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

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

6
17.08.2008 18:17:12

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

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

6
23.05.2017 12:34:59

То же самое, но в 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
}
1
7.02.2016 00:33:23

То, что я в конечном итоге сделал:

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;
}
0
7.02.2016 00:34:34
Вы должны рассмотреть использование блока для файла
VoteCoffee 28.04.2014 19:04:42
Используйте System.Threading.Thread.Sleep(1000)вместо new System.Threading.ManualResetEvent(false).WaitOne(1000)
Endrju 12.08.2014 11:41:57