나는 약간의 코드를 가지고 있고 그것이 실행될 때 IOException
,
프로세스가 다른 프로세스에서 사용 중이므로 'filename'파일에 액세스 할 수 없습니다.
이것은 무엇을 의미하며 이에 대해 무엇을 할 수 있습니까?
나는 약간의 코드를 가지고 있고 그것이 실행될 때 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 프로세스는 동일한 파일에서 모두 읽을 수 있지만 하나만 작성해야합니다. 제어 된 시나리오에서는 동시 쓰기를 활성화 할 수도 있지만이 답변의 텍스트 단락에서는 일반화 할 수 없습니다.
다른 프로세스에서 사용하는 파일의 잠금 을 해제 할 수 있습니까? 항상 안전하지는 않지만 쉽지는 않지만 가능합니다 .
File.Create(path)
추가해야 .Close()
합니다. using
파일을 작성한 후 삭제하는 명령문 외에도 이와 같은 함정이 있습니다. 파일을 만들고 삭제하는 방법에 대한 질문에 코드를 게시해야합니다. 그러나 아마도 위에서 언급 한 것과 일치 할 것입니다.
Directory.SetCreationTimeUTC()
하지만 파일 탐색기가 열려 있으면 다른 프로세스에서 디렉토리에 액세스한다고 주장하면서 실패합니다. 이 상황을 어떻게 처리해야합니까?
이미지를 업로드하는 중에 문제가 발생하여 이미지를 삭제하지 못해 해결책을 찾았습니다. gl hf
//C# .NET
var image = Image.FromFile(filePath);
image.Dispose(); // this removes all resources
//later...
File.Delete(filePath); //now works
파일 이름이없는 파일 경로로 이동하려면 대상에서 전체 경로를 지정해야합니다.
오류는 다른 프로세스가 파일에 액세스하려고 함을 나타냅니다. 글을 쓰려고 시도하는 동안 자신이나 다른 사람이 열었을 수 있습니다. "읽기"또는 "복사"는 일반적으로이를 발생시키지 않지만, 쓰거나 삭제를 호출 할 수 있습니다.
다른 답변에서 언급했듯이 이것을 피할 수있는 몇 가지 기본 사항이 있습니다.
에서 FileStream
운영하는에 배치 using
A를 블록 FileShare.ReadWrite
액세스 모드.
예를 들면 다음과 같습니다.
using (FileStream stream = File.Open(path, FileMode.Open, FileAccess.Write, FileShare.ReadWrite))
{
}
참고 FileAccess.ReadWrite
사용이 불가능합니다 FileMode.Append
.
File.SaveAs
파일이 사용 중일 때 입력 스트림을 사용 하여이 문제를 겪었습니다 . 필자의 경우 실제로 파일 시스템에 파일을 다시 저장할 필요가 없으므로 그냥 제거했지만 코드와 같이 using
문 에서 FileStream을 만들려고했을 수 FileAccess.ReadWrite
있습니다. 위.
더 이상 사용하지 않는 데이터를 다른 파일로 저장하고 이전 파일을 삭제하기 위해 다시 돌아 가면 원래 파일 이름으로 성공적으로 저장된 파일의 이름을 바꾸는 것이 옵션입니다. 사용중인 파일을 테스트하는 방법은
List<Process> lstProcs = ProcessHandler.WhoIsLocking(file);
아래 코드 내 라인에 표시되며 Windows 서비스에서 루프로 수행 할 수 있습니다. 파일을 교체 할 때 정기적으로보고 삭제하려는 특정 파일이있는 경우. 항상 같은 파일을 가지고 있지 않은 경우 서비스에서 항상 파일 이름을 확인한 다음 해당 프로세스를 검사하고 그에 따라 프로세스 종료 및 삭제를 수행하도록 텍스트 파일 또는 데이터베이스 테이블을 업데이트 할 수 있습니다. 다음 옵션에서. 물론 프로세스 삭제 및 종료를 수행하려면 주어진 컴퓨터에 대한 관리자 권한이있는 계정 사용자 이름과 비밀번호가 필요합니다.
파일을 저장하려고 할 때 파일이 사용 중인지 알 수없는 경우 저장하기 전에 Word와 같은 경우 파일과 같이 파일을 사용할 수있는 모든 프로세스를 닫을 수 있습니다.
로컬 인 경우 다음을 수행 할 수 있습니다.
ProcessHandler.localProcessKill("winword.exe");
원격 인 경우 다음을 수행 할 수 있습니다.
ProcessHandler.remoteProcessKill(computerName, txtUserName, txtPassword, "winword.exe");
여기서는 txtUserName
의 형태이다 DOMAIN\user
.
파일을 잠그는 프로세스 이름을 모른다고 가정 해 봅시다. 그런 다음이 작업을 수행 할 수 있습니다.
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
}
}
File.Move()
작동하지 않으며 동일한 오류가 발생합니다. 전자 메일에 파일을 추가하면 Attachments.Add()
작업 중 사용 중 파일 이 복사 작업이기 때문에 오류가 있다고 생각하지 않습니다 . 어떤 이유로 든 임시 디렉토리에 복사하고 사본을 첨부 한 후 복사 된 파일을 삭제하십시오. 그러나 OP가 파일을 수정하고 사용하려는 경우 이러한 종류의 솔루션 (코드를 표시하지 않고 첨부 부분 만)이 효과가 있다고 생각하지 않습니다. .Dispose()
파일은 이전 작업에서 열리지 않는 한 항상 좋은 생각이지만 여기서는 관련이 없습니다.
다음과 같은 시나리오에서 동일한 오류가 발생했습니다.
대부분의 파일은 크기가 작았지만 일부는 크기가 커서 삭제하려고하면 파일에 액세스 할 수 없습니다 . 오류가 발생했습니다.
그러나 찾기가 쉽지는 않았지만 해결책은 "작업이 완료 되기를 기다리는 " 것만 큼 간단했습니다 .
using (var wc = new WebClient())
{
var tskResult = wc.UploadFileTaskAsync(_address, _fileName);
tskResult.Wait();
}
내 아래 코드는이 문제를 해결하지만 먼저이 문제의 원인을 이해하고 코드를 변경하여 찾을 수있는 솔루션을 시도해야한다고 제안합니다.
이 문제를 해결하는 또 다른 방법을 줄 수는 있지만 더 나은 해결책은 코딩 구조를 확인하고 어떤 일이 발생하는지 분석하는 것입니다. 어떤 해결책을 찾지 못하면 아래 코드를 사용할 수 있습니다
try{
Start:
///Put your file access code here
}catch (Exception ex)
{
//by anyway you need to handle this error with below code
if (ex.Message.StartsWith("The process cannot access the file"))
{
//Wait for 5 seconds to free that file and then start execution again
Thread.Sleep(5000);
goto Start;
}
}
GC.*()
코드에 다른 문제가있을 수 있습니다. 2) 메시지가 현지화되고 깨지기 쉬우 므로 대신 HRESULT를 사용하십시오. 3) 당신과 함께 자고 싶을 수도 있습니다 Task.Delay()
(그리고 많은 경우 10 초가 과도합니다). 4) 종료 조건이 없습니다.이 코드는 계속 중단 될 수 있습니다. 5) 당신은 확실히 goto
여기 필요하지 않습니다 . 6) 잡는 Exception
것은 일반적으로 나쁜 생각입니다.이 경우에도 ... 6) 다른 일이 발생하면 오류를 삼킨 것입니다.
GC.Collect()
는 일부 COM 객체를 다룰 때였습니다.