[C#] IOException : 프로세스가 ‘file path’파일을 다른 프로세스에서 사용 중이므로 파일에 액세스 할 수 없습니다.

나는 약간의 코드를 가지고 있고 그것이 실행될 때 IOException,

프로세스가 다른 프로세스에서 사용 중이므로 ‘filename’파일에 액세스 할 수 없습니다.

이것은 무엇을 의미하며 이에 대해 무엇을 할 수 있습니까?



답변

원인이 무엇입니까?

오류 메시지는 매우 분명합니다. 파일에 액세스하려고하는데 다른 프로세스 (또는 동일한 프로세스)가 파일로 무언가를하고 있기 때문에 액세스 할 수 없습니다 (그리고 공유를 허용하지 않았습니다).

디버깅

특정 시나리오에 따라 해결하기가 매우 쉬울 수 있습니다. 좀 봅시다

프로세스는 해당 파일에 액세스 할
있는 유일한 프로세스입니다. 다른 프로세스는 자신의 프로세스입니다. 프로그램의 다른 부분에서 해당 파일을 열었다는 것을 알고 있다면, 매번 사용 후 파일 핸들을 올바르게 닫았는지 확인해야합니다. 이 버그가있는 코드의 예는 다음과 같습니다.

var stream = new FileStream(path, FileAccess.Read);
var reader = new StreamReader(stream);
// Read data from this file, when I'm done I don't need it any more
File.Delete(path); // IOException: file is in use

다행히도를 FileStream구현 IDisposable하므로 모든 코드를 using명령문으로 쉽게 묶을 수 있습니다 .

using (var stream = File.Open("myfile.txt", FileMode.Open)) {
    // Use stream
}

// Here stream is not accessible and it has been closed (also if
// an exception is thrown and stack unrolled

이 패턴은 또한 예외가 발생할 경우 파일이 열린 상태로 유지되지 않도록합니다 (파일이 사용중인 이유 일 수 있습니다. 문제가 발생하여 아무도 닫지 않았습니다 . 예를 보려면 이 게시물 참조 ).

모든 것이 정상으로 보이면 (예외의 경우에도 열려있는 모든 파일을 항상 닫을 수 있음) 여러 작업 스레드가있는 경우 두 가지 옵션이 있습니다. 코드를 재 작업하여 파일 액세스를 직렬화합니다 (항상 가능한 것은 아니며 항상 그렇지는 않습니다) 원하는) 또는 재시도 패턴을 적용 하십시오 . I / O 작업의 일반적인 패턴입니다. 무언가를 시도하고 오류가 발생하면 기다렸다가 다시 시도하십시오 (예를 들어, Windows Shell이 ​​파일을 사용하고 있음을 알리는 데 시간이 걸리는 이유를 스스로에게 묻었습니까?) 삭제할 수 없습니까?). C #에서는 구현하기가 매우 쉽습니다 ( 디스크 I / O , 네트워킹데이터베이스 액세스에 대한 더 나은 예제도 참조하십시오 ).

private const int NumberOfRetries = 3;
private const int DelayOnRetry = 1000;

for (int i=1; i <= NumberOfRetries; ++i) {
    try {
        // Do stuff with file
        break; // When done we can break loop
    }
    catch (IOException e) when (i <= NumberOfRetries) {
        // You may check error code to filter some exceptions, not every error
        // can be recovered.
        Thread.Sleep(DelayOnRetry);
    }
}

StackOverflow에서 자주 발생하는 일반적인 오류에 유의하십시오.

var stream = File.Open(path, FileOpen.Read);
var content = File.ReadAllText(path);

이 경우 ReadAllText()파일이 사용 중이기 때문에 실패합니다 ( File.Open()이전 줄에서). 파일을 미리 여는 것은 불필요 할뿐만 아니라 잘못된 것입니다. 같은 모든 적용 File반환하지 않는 기능 핸들을 작업중인 파일 : File.ReadAllText(), File.WriteAllText(), File.ReadAllLines(), File.WriteAllLines()과 (같은 다른 File.AppendAllXyz()기능) 모든 열고 스스로 파일을 닫습니다.

프로세스가 해당 파일에 액세스 할 수있는 유일한 프로세스가 아닙니다 프로세스가 해당 파일에 액세스하는 유일한 프로세스가 아닌
경우 상호 작용이 더 어려울 수 있습니다. 재시도 패턴 (파일이 다른 사람이 열 수 없습니다하는 경우는 있지만, 당신은 확인 프로세스 탐색기와 같은 유틸리티를 필요로한다 도움이 될 것입니다 누가 하고있는 ).

피하는 방법

해당되는 경우 항상 using 문을 사용 하여 파일을 엽니 다. 이전 단락에서 말했듯이 많은 일반적인 오류를 피하는 데 적극적으로 도움이됩니다 ( 이 게시물 을 사용하지 않는 방법 에 대한 예는 이 게시물 참조 ).

가능하면 특정 파일에 대한 액세스 권한을 가진 사람을 결정하고 잘 알려진 몇 가지 방법을 통해 액세스를 중앙 집중화하십시오. 예를 들어, 프로그램이 읽고 쓰는 데이터 파일이있는 경우 모든 I / O 코드를 단일 클래스 안에 넣어야합니다. 디버그가 쉬워집니다 (항상 중단 점을두고 누가 무엇을하고 있는지 알 수 있기 때문에). 또한 다중 액세스를위한 동기화 지점 (필요한 경우)이 될 것입니다.

I / O 작업이 항상 실패 할 수 있다는 것을 잊지 마십시오. 일반적인 예는 다음과 같습니다.

if (File.Exists(path))
    File.Delete(path);

경우 누군가가 후 파일을 삭제 File.Exists()하지만, 전에 File.Delete(), 다음은을 던질거야 IOException당신이 잘못 안전 느낄 수있는 장소에.

가능할 때마다 재시도 패턴을 적용 FileSystemWatcher하고을 사용하는 경우 연기 조치를 고려하십시오 (알림을받을 수는 있지만 애플리케이션이 해당 파일에서만 독점적으로 작동 할 수 있음).

고급 시나리오
항상 쉬운 것은 아니므로 다른 사람과 액세스를 공유해야 할 수도 있습니다. 예를 들어 처음부터 끝까지 읽고 쓰는 경우 최소한 두 가지 옵션이 있습니다.

1) 스레드 안전하지FileStream 않기 때문에 적절한 동기화 기능으로 공유하십시오 . 참조 예제는 게시물.

2) FileShare열거를 사용하여 다른 프로세스 (또는 자체 프로세스의 다른 부분)가 동일한 파일에 동시에 액세스 할 수 있도록 OS에 지시하십시오.

using (var stream = File.Open(path, FileMode.Open, FileAccess.Write, FileShare.Read))
{
}

이 예제에서는 쓰기 위해 파일을 열고 읽기 위해 공유하는 방법을 보여주었습니다. 읽고 쓸 때 겹치면 데이터가 정의되지 않거나 유효하지 않습니다. 읽을 때 처리해야하는 상황입니다. 또한 이것은 stream스레드 안전에 액세스하지 않으므로 액세스가 어떻게 든 동기화되지 않으면이 객체를 여러 스레드와 공유 할 수 없습니다 (이전 링크 참조). 다른 공유 옵션을 사용할 수 있으며 더 복잡한 시나리오를 엽니 다. 자세한 내용은 MSDN 을 참조하십시오.

일반적으로 N 프로세스는 동일한 파일에서 모두 읽을 수 있지만 하나만 작성해야합니다. 제어 된 시나리오에서는 동시 쓰기를 활성화 할 수도 있지만이 답변의 텍스트 단락에서는 일반화 할 수 없습니다.

다른 프로세스에서 사용하는 파일의 잠금해제 할 수 있습니까? 항상 안전하지는 않지만 쉽지는 않지만 가능합니다 .


답변

FileShare를 사용하면 다른 프로세스에서 파일을 열었더라도 파일을 여는 문제가 해결되었습니다.

using (var stream = File.Open(path, FileMode.Open, FileAccess.Write, FileShare.ReadWrite))
{
}


답변

이미지를 업로드하는 중에 문제가 발생하여 이미지를 삭제하지 못해 해결책을 찾았습니다. gl hf

//C# .NET
var image = Image.FromFile(filePath);

image.Dispose(); // this removes all resources

//later...

File.Delete(filePath); //now works


답변

파일 이름이없는 파일 경로로 이동하려면 대상에서 전체 경로를 지정해야합니다.


답변

오류는 다른 프로세스가 파일에 액세스하려고 함을 나타냅니다. 글을 쓰려고 시도하는 동안 자신이나 다른 사람이 열었을 수 있습니다. “읽기”또는 “복사”는 일반적으로이를 발생시키지 않지만, 쓰거나 삭제를 호출 할 수 있습니다.

다른 답변에서 언급했듯이 이것을 피할 수있는 몇 가지 기본 사항이 있습니다.

  1. 에서 FileStream운영하는에 배치 usingA를 블록 FileShare.ReadWrite액세스 모드.

    예를 들면 다음과 같습니다.

    using (FileStream stream = File.Open(path, FileMode.Open, FileAccess.Write, FileShare.ReadWrite))
    {
    }

    참고 FileAccess.ReadWrite사용이 불가능합니다 FileMode.Append.

  2. File.SaveAs파일이 사용 중일 때 입력 스트림을 사용 하여이 문제를 겪었습니다 . 필자의 경우 실제로 파일 시스템에 파일을 다시 저장할 필요가 없으므로 그냥 제거했지만 코드와 같이 using문 에서 FileStream을 만들려고했을 수 FileAccess.ReadWrite있습니다. 위.

  3. 더 이상 사용하지 않는 데이터를 다른 파일로 저장하고 이전 파일을 삭제하기 위해 다시 돌아 가면 원래 파일 이름으로 성공적으로 저장된 파일의 이름을 바꾸는 것이 옵션입니다. 사용중인 파일을 테스트하는 방법은

    List<Process> lstProcs = ProcessHandler.WhoIsLocking(file);

    아래 코드 내 라인에 표시되며 Windows 서비스에서 루프로 수행 할 수 있습니다. 파일을 교체 할 때 정기적으로보고 삭제하려는 특정 파일이있는 경우. 항상 같은 파일을 가지고 있지 않은 경우 서비스에서 항상 파일 이름을 확인한 다음 해당 프로세스를 검사하고 그에 따라 프로세스 종료 및 삭제를 수행하도록 텍스트 파일 또는 데이터베이스 테이블을 업데이트 할 수 있습니다. 다음 옵션에서. 물론 프로세스 삭제 및 종료를 수행하려면 주어진 컴퓨터에 대한 관리자 권한이있는 계정 사용자 이름과 비밀번호가 필요합니다.

  4. 파일을 저장하려고 할 때 파일이 사용 중인지 알 수없는 경우 저장하기 전에 Word와 같은 경우 파일과 같이 파일을 사용할 수있는 모든 프로세스를 닫을 수 있습니다.

    로컬 인 경우 다음을 수행 할 수 있습니다.

    ProcessHandler.localProcessKill("winword.exe");

    원격 인 경우 다음을 수행 할 수 있습니다.

    ProcessHandler.remoteProcessKill(computerName, txtUserName, txtPassword, "winword.exe");

    여기서는 txtUserName의 형태이다 DOMAIN\user.

  5. 파일을 잠그는 프로세스 이름을 모른다고 가정 해 봅시다. 그런 다음이 작업을 수행 할 수 있습니다.

    List<Process> lstProcs = new List<Process>();
    lstProcs = ProcessHandler.WhoIsLocking(file);
    
    foreach (Process p in lstProcs)
    {
        if (p.MachineName == ".")
            ProcessHandler.localProcessKill(p.ProcessName);
        else
            ProcessHandler.remoteProcessKill(p.MachineName, txtUserName, txtPassword, p.ProcessName);
    }

    참고 file:의 UNC 경로 여야합니다 \\computer\share\yourdoc.docx(가)의 순서 Process가에 무엇을 컴퓨터 파악하고 p.MachineName유효.

    다음은이 함수가 사용하는 클래스이며에 대한 참조를 추가해야합니다 System.Management. 이 코드는 원래 Eric J가 작성했습니다 .

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Runtime.InteropServices;
    using System.Diagnostics;
    using System.Management;
    
    namespace MyProject
    {
        public static class ProcessHandler
        {
            [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;
            }
    
            public static void remoteProcessKill(string computerName, string userName, string pword, string processName)
            {
                var connectoptions = new ConnectionOptions();
                connectoptions.Username = userName;
                connectoptions.Password = pword;
    
                ManagementScope scope = new ManagementScope(@"\\" + computerName + @"\root\cimv2", connectoptions);
    
                // WMI query
                var query = new SelectQuery("select * from Win32_process where name = '" + processName + "'");
    
                using (var searcher = new ManagementObjectSearcher(scope, query))
                {
                    foreach (ManagementObject process in searcher.Get())
                    {
                        process.InvokeMethod("Terminate", null);
                        process.Dispose();
                    }
                }
            }
    
            public static void localProcessKill(string processName)
            {
                foreach (Process p in Process.GetProcessesByName(processName))
                {
                    p.Kill();
                }
            }
    
            [DllImport("kernel32.dll")]
            public static extern bool MoveFileEx(string lpExistingFileName, string lpNewFileName, int dwFlags);
    
            public const int MOVEFILE_DELAY_UNTIL_REBOOT = 0x4;
    
        }
    }

답변

이 스레드의 다른 답변에서 지적 했듯이이 오류를 해결하려면 파일이 잠겨있는 위치를 이해하기 위해 코드를주의 깊게 검사해야합니다.

필자의 경우 이동 작업을 수행하기 전에 파일을 전자 메일 첨부 파일로 전송했습니다.

따라서 SMTP 클라이언트가 이메일 전송을 완료 할 때까지 파일이 몇 초 동안 잠겼습니다.

내가 채택한 솔루션은 먼저 파일이동 한 다음 이메일을 보내는 것입니다. 이것은 나를 위해 문제를 해결했습니다.

앞에서 허드슨이 지적한 다른 가능한 해결책은 사용 후 물체를 처리하는 것이 었습니다.

public static SendEmail()
{
           MailMessage mMailMessage = new MailMessage();
           //setup other email stuff

            if (File.Exists(attachmentPath))
            {
                Attachment attachment = new Attachment(attachmentPath);
                mMailMessage.Attachments.Add(attachment);
                attachment.Dispose(); //disposing the Attachment object
            }
} 


답변

다음과 같은 시나리오에서 동일한 오류가 발생했습니다.

  • 서버에 파일 업로드
  • 그런 다음 이전 파일을 업로드 한 후 제거하십시오.

대부분의 파일은 크기가 작았지만 일부는 크기가 커서 삭제하려고하면 파일에 액세스 할 수 없습니다 . 오류가 발생했습니다.

그러나 찾기가 쉽지는 않았지만 해결책은 “작업이 완료 되기를 기다리는 ” 것만 큼 ​​간단했습니다 .

using (var wc = new WebClient())
{
   var tskResult = wc.UploadFileTaskAsync(_address, _fileName);
   tskResult.Wait();
}