1 이미지 파일에 반복적으로 액세스 해야하는 프로그램을 C #으로 작성 중입니다. 대부분의 경우 작동하지만 내 컴퓨터가 빠르게 실행되는 경우 파일 시스템에 다시 저장하기 전에 파일에 액세스하려고 시도하고 "다른 프로세스에서 사용중인 파일" 오류가 발생 합니다.
이 문제를 해결할 방법을 찾고 싶지만 내 모든 인터넷 검색은 예외 처리를 사용하여 검사를 만들었습니다. 이것은 내 종교에 위배되므로 누군가 더 좋은 방법이 있는지 궁금합니다.
1 이미지 파일에 반복적으로 액세스 해야하는 프로그램을 C #으로 작성 중입니다. 대부분의 경우 작동하지만 내 컴퓨터가 빠르게 실행되는 경우 파일 시스템에 다시 저장하기 전에 파일에 액세스하려고 시도하고 "다른 프로세스에서 사용중인 파일" 오류가 발생 합니다.
이 문제를 해결할 방법을 찾고 싶지만 내 모든 인터넷 검색은 예외 처리를 사용하여 검사를 만들었습니다. 이것은 내 종교에 위배되므로 누군가 더 좋은 방법이 있는지 궁금합니다.
답변:
이 솔루션에 대한 업데이트 된 참고 : FileAccess.ReadWrite
파일을 검사 하면 읽기 전용 파일이 검사 되지 않으므로 솔루션을 검사하도록 수정되었습니다 FileAccess.Read
. FileAccess.Read
파일에 쓰기 또는 읽기 잠금이있는 경우 확인 시도 가 실패 하기 때문에이 솔루션이 작동하지만 파일에 쓰기 또는 읽기 잠금이없는 경우 (예 : 파일이 열려있는 경우)이 솔루션이 작동하지 않습니다. FileShare.Read 또는 FileShare.Write 액세스 권한으로 (읽기 또는 쓰기 용)
ORIGINAL : 지난 몇 년 동안이 코드를 사용해 왔으며 아무런 문제가 없었습니다.
예외 사용에 대한 망설임을 이해하지만 항상 예외를 피할 수는 없습니다.
protected virtual bool IsFileLocked(FileInfo file)
{
try
{
using(FileStream stream = file.Open(FileMode.Open, FileAccess.Read, FileShare.None))
{
stream.Close();
}
}
catch (IOException)
{
//the file is unavailable because it is:
//still being written to
//or being processed by another thread
//or does not exist (has already been processed)
return true;
}
//file is not locked
return false;
}
보안 취약점으로 사용되는 문서화 된 예제가있는 스레드 경쟁 조건이 발생할 수 있습니다. 파일이 사용 가능한지 확인한 다음 파일을 사용하여 사용하려고하면 악의적 인 사용자가 코드를 강제로 이용하고 악용 할 수있는 시점에이를 던질 수 있습니다.
가장 좋은 방법은 try catch / finally 파일 핸들을 얻는 것입니다.
try
{
using (Stream stream = new FileStream("MyFilename.txt", FileMode.Open))
{
// File/Stream manipulating code here
}
} catch {
//check here why it failed and ask user to retry if the file is in use.
}
이를 사용하여 파일이 잠겨 있는지 확인하십시오.
using System.IO;
using System.Runtime.InteropServices;
internal static class Helper
{
const int ERROR_SHARING_VIOLATION = 32;
const int ERROR_LOCK_VIOLATION = 33;
private static bool IsFileLocked(Exception exception)
{
int errorCode = Marshal.GetHRForException(exception) & ((1 << 16) - 1);
return errorCode == ERROR_SHARING_VIOLATION || errorCode == ERROR_LOCK_VIOLATION;
}
internal static bool CanReadFile(string filePath)
{
//Try-Catch so we dont crash the program and can check the exception
try {
//The "using" is important because FileStream implements IDisposable and
//"using" will avoid a heap exhaustion situation when too many handles
//are left undisposed.
using (FileStream fileStream = File.Open(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None)) {
if (fileStream != null) fileStream.Close(); //This line is me being overly cautious, fileStream will never be null unless an exception occurs... and I know the "using" does it but its helpful to be explicit - especially when we encounter errors - at least for me anyway!
}
}
catch (IOException ex) {
//THE FUNKY MAGIC - TO SEE IF THIS FILE REALLY IS LOCKED!!!
if (IsFileLocked(ex)) {
// do something, eg File.Copy or present the user with a MsgBox - I do not recommend Killing the process that is locking the file
return false;
}
}
finally
{ }
return true;
}
}
성능상의 이유로 동일한 작업으로 파일 내용을 읽는 것이 좋습니다. 여기 몇 가지 예가 있어요.
public static byte[] ReadFileBytes(string filePath)
{
byte[] buffer = null;
try
{
using (FileStream fileStream = File.Open(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
{
int length = (int)fileStream.Length; // get file length
buffer = new byte[length]; // create buffer
int count; // actual number of bytes read
int sum = 0; // total number of bytes read
// read until Read method returns 0 (end of the stream has been reached)
while ((count = fileStream.Read(buffer, sum, length - sum)) > 0)
sum += count; // sum is a buffer offset for next reading
fileStream.Close(); //This is not needed, just me being paranoid and explicitly releasing resources ASAP
}
}
catch (IOException ex)
{
//THE FUNKY MAGIC - TO SEE IF THIS FILE REALLY IS LOCKED!!!
if (IsFileLocked(ex))
{
// do something?
}
}
catch (Exception ex)
{
}
finally
{
}
return buffer;
}
public static string ReadFileTextWithEncoding(string filePath)
{
string fileContents = string.Empty;
byte[] buffer;
try
{
using (FileStream fileStream = File.Open(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
{
int length = (int)fileStream.Length; // get file length
buffer = new byte[length]; // create buffer
int count; // actual number of bytes read
int sum = 0; // total number of bytes read
// read until Read method returns 0 (end of the stream has been reached)
while ((count = fileStream.Read(buffer, sum, length - sum)) > 0)
{
sum += count; // sum is a buffer offset for next reading
}
fileStream.Close(); //Again - this is not needed, just me being paranoid and explicitly releasing resources ASAP
//Depending on the encoding you wish to use - I'll leave that up to you
fileContents = System.Text.Encoding.Default.GetString(buffer);
}
}
catch (IOException ex)
{
//THE FUNKY MAGIC - TO SEE IF THIS FILE REALLY IS LOCKED!!!
if (IsFileLocked(ex))
{
// do something?
}
}
catch (Exception ex)
{
}
finally
{ }
return fileContents;
}
public static string ReadFileTextNoEncoding(string filePath)
{
string fileContents = string.Empty;
byte[] buffer;
try
{
using (FileStream fileStream = File.Open(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
{
int length = (int)fileStream.Length; // get file length
buffer = new byte[length]; // create buffer
int count; // actual number of bytes read
int sum = 0; // total number of bytes read
// read until Read method returns 0 (end of the stream has been reached)
while ((count = fileStream.Read(buffer, sum, length - sum)) > 0)
{
sum += count; // sum is a buffer offset for next reading
}
fileStream.Close(); //Again - this is not needed, just me being paranoid and explicitly releasing resources ASAP
char[] chars = new char[buffer.Length / sizeof(char) + 1];
System.Buffer.BlockCopy(buffer, 0, chars, 0, buffer.Length);
fileContents = new string(chars);
}
}
catch (IOException ex)
{
//THE FUNKY MAGIC - TO SEE IF THIS FILE REALLY IS LOCKED!!!
if (IsFileLocked(ex))
{
// do something?
}
}
catch (Exception ex)
{
}
finally
{
}
return fileContents;
}
직접 해보십시오.
byte[] output1 = Helper.ReadFileBytes(@"c:\temp\test.txt");
string output2 = Helper.ReadFileTextWithEncoding(@"c:\temp\test.txt");
string output3 = Helper.ReadFileTextNoEncoding(@"c:\temp\test.txt");
IOException
일반 대신에 켜져 있어야하며 Exception
유형에 대한 테스트 여야합니다 .
IOException
일반적인 것을 따랐습니다 . 일반적인 것은 지나가는 모든 것을 잡을 것이고 특정 IOException
은 항상 외로울 것입니다. 그냥 둘을 바꾸십시오.
의도 한대로 예외를 사용하십시오. 파일이 사용 중임을 승인하고 조치가 완료 될 때까지 반복하여 다시 시도하십시오. 또한 작동하기 전에 상태를 확인하는주기를 낭비하지 않기 때문에 가장 효율적입니다.
예를 들어 아래 기능을 사용하십시오.
TimeoutFileAction(() => { System.IO.File.etc...; return null; } );
2 초 후에 시간 초과되는 재사용 가능한 방법
private T TimeoutFileAction<T>(Func<T> func)
{
var started = DateTime.UtcNow;
while ((DateTime.UtcNow - started).TotalMilliseconds < 2000)
{
try
{
return func();
}
catch (System.IO.IOException exception)
{
//ignore, or log somewhere if you want to
}
}
return default(T);
}
아마도 FileSystemWatcher를 사용 하여 Changed 이벤트를 감시 할 수 있습니다 .
나는 이것을 직접 사용하지는 않았지만 기회가 될 수도 있습니다. 이 경우 파일 시스템 감시자가 약간 무겁다면 try / catch / sleep 루프를 사용합니다.
스트림이 제공되는 즉시 스트림을 제공하는 작업을 반환 할 수 있습니다. 간단한 솔루션이지만 좋은 출발점입니다. 스레드 안전합니다.
private async Task<Stream> GetStreamAsync()
{
try
{
return new FileStream("sample.mp3", FileMode.Open, FileAccess.Write);
}
catch (IOException)
{
await Task.Delay(TimeSpan.FromSeconds(1));
return await GetStreamAsync();
}
}
평소처럼이 스트림을 사용할 수 있습니다.
using (var stream = await FileStreamGetter.GetStreamAsync())
{
Console.WriteLine(stream.Length);
}
GetStreamAsync()
입니까?
내가 아는 유일한 방법은 너무 빠르지 않지만 예제가있는 Win32 독점 잠금 API를 사용하는 것입니다.
대부분의 사람들은 이것에 대한 간단한 해결책을 위해 단순히 루프를 시도 / 잡기 / 수면합니다.
static bool FileInUse(string path)
{
try
{
using (FileStream fs = new FileStream(path, FileMode.OpenOrCreate))
{
fs.CanWrite
}
return false;
}
catch (IOException ex)
{
return true;
}
}
string filePath = "C:\\Documents And Settings\\yourfilename";
bool isFileInUse;
isFileInUse = FileInUse(filePath);
// Then you can do some checking
if (isFileInUse)
Console.WriteLine("File is in use");
else
Console.WriteLine("File is not in use");
도움이 되었기를 바랍니다!
위의 허용 된 대답은 FileShare.Read 모드로 쓰기 위해 파일을 열었거나 파일에 읽기 전용 속성이있는 경우 코드가 작동하지 않는 문제가 있습니다. 이 수정 된 솔루션은 다음 두 가지 사항을 명심해야합니다 (허용 된 솔루션의 경우도 마찬가지).
위의 사항을 염두에두면 파일 쓰기 가 잠겨 있는지 또는 읽기를 방지하기 위해 잠겨 있는지 확인합니다 .
public static bool FileLocked(string FileName)
{
FileStream fs = null;
try
{
// NOTE: This doesn't handle situations where file is opened for writing by another process but put into write shared mode, it will not throw an exception and won't show it as write locked
fs = File.Open(FileName, FileMode.Open, FileAccess.ReadWrite, FileShare.None); // If we can't open file for reading and writing then it's locked by another process for writing
}
catch (UnauthorizedAccessException) // https://msdn.microsoft.com/en-us/library/y973b725(v=vs.110).aspx
{
// This is because the file is Read-Only and we tried to open in ReadWrite mode, now try to open in Read only mode
try
{
fs = File.Open(FileName, FileMode.Open, FileAccess.Read, FileShare.None);
}
catch (Exception)
{
return true; // This file has been locked, we can't even open it to read
}
}
catch (Exception)
{
return true; // This file has been locked
}
finally
{
if (fs != null)
fs.Close();
}
return false;
}
3 줄짜리 작업을 제외하고 참조 용으로 : 완전한 정보 를 원한다면 Microsoft Dev Center에 약간의 프로젝트가 있습니다.
https://code.msdn.microsoft.com/windowsapps/How-to-know-the-process-704839f4
소개에서 :
.NET Framework 4.0에서 개발 된 C # 샘플 코드는 파일을 잠그는 프로세스를 찾는 데 도움이됩니다. rstrtmgr.dll 에 포함 된 RmStartSession 함수는 재시작 관리자 세션을 작성하는 데 사용되었으며 리턴 결과에 따라 Win32Exception 오브젝트의 새 인스턴스가 작성됩니다. RmRegisterRescources 함수 를 통해 자원을 다시 시작 관리자 세션에 등록한 후 RmGetList 함수가 호출되어 RM_PROCESS_INFO 배열 을 열거하여 응용 프로그램이 특정 파일을 사용 중인지 확인합니다 .
"재시작 관리자 세션"에 연결하여 작동합니다.
재시작 관리자는 세션에 등록 된 자원 목록을 사용하여 종료 및 재시작해야하는 응용 프로그램 및 서비스를 판별합니다. 리소스는 파일 이름, 서비스 짧은 이름 또는 실행중인 응용 프로그램을 설명하는 RM_UNIQUE_PROCESS 구조로 식별 할 수 있습니다 .
그것은 당신의 특정 요구에 대해 약간 과도하게 설계 되었을 수도 있습니다 ... 그러나 그것이 당신이 원하는 것이라면 , 계속 진행하여 vs 프로젝트를 잡으십시오.
내 경험상 일반적 으로이 작업을 수행 한 다음 파일을 '보호'하여 멋진 것을 수행하고 '보호 된'파일을 사용하려고합니다. 이와 같이 사용하려는 파일이 하나만 있으면 Jeremy Thompson의 답변에 설명 된 트릭을 사용할 수 있습니다. 그러나 설치 프로그램을 작성하는 등의 많은 파일에서이 작업을 수행하려고하면 약간의 피해를 입게됩니다.
이 문제를 해결할 수있는 매우 우아한 방법은 파일 시스템에서 폴더 이름을 사용하는 경우 파일 시스템에서 폴더 이름을 변경할 수 없다는 사실을 사용하는 것입니다. 폴더를 동일한 파일 시스템에 보관하면 매력처럼 작동합니다.
이것이 악용 될 수있는 명백한 방법을 알고 있어야합니다. 결국 파일이 잠기지 않습니다. 또한 Move
작업이 실패 할 수있는 다른 이유도 있습니다 . 분명히 적절한 오류 처리 (MSDN)가 도움이 될 수 있습니다.
var originalFolder = @"c:\myHugeCollectionOfFiles"; // your folder name here
var someFolder = Path.Combine(originalFolder, "..", Guid.NewGuid().ToString("N"));
try
{
Directory.Move(originalFolder, someFolder);
// Use files
}
catch // TODO: proper exception handling
{
// Inform user, take action
}
finally
{
Directory.Move(someFolder, originalFolder);
}
개별 파일의 경우 Jeremy Thompson이 게시 한 잠금 제안을 고수합니다.
FileShare
잠금을 올바르게 사용 하고 확인하기 때문에 귀하의 솔루션을 사용합니다 .
가장 잘 알 수있는 한 허용되는 답변과 동일한 코드이지만 코드는 적습니다.
public static bool IsFileLocked(string file)
{
try
{
using (var stream = File.OpenRead(file))
return false;
}
catch (IOException)
{
return true;
}
}
그러나 다음과 같은 방식으로 수행하는 것이 더 강력하다고 생각합니다.
public static void TryToDoWithFileStream(string file, Action<FileStream> action,
int count, int msecTimeOut)
{
FileStream stream = null;
for (var i = 0; i < count; ++i)
{
try
{
stream = File.OpenRead(file);
break;
}
catch (IOException)
{
Thread.Sleep(msecTimeOut);
}
}
action(stream);
}
내 라이브러리를 사용하여 여러 앱에서 파일에 액세스 할 수 있습니다.
Nuget에서 설치할 수 있습니다 : Install-Package Xabe.FileLock
그것에 대한 자세한 정보를 원하시면 https://github.com/tomaszzmuda/Xabe.FileLock 을 확인 하십시오.
ILock fileLock = new FileLock(file);
if(fileLock.Acquire(TimeSpan.FromSeconds(15), true))
{
using(fileLock)
{
// file operations here
}
}
fileLock.Acquire 메소드는이 객체 전용 파일을 잠글 수있는 경우에만 true를 반환합니다. 그러나 파일을 업로드하는 앱은 파일 잠금에서도 수행해야합니다. 객체에 접근 할 수없는 경우 metod는 false를 반환합니다.
한때 온라인 백업 아카이브에 PDF를 업로드해야했습니다. 그러나 사용자가 다른 프로그램 (예 : PDF 리더)에서 파일을 열면 백업이 실패합니다. 서두르 면서이 스레드에서 몇 가지 주요 답변을 시도했지만 작동하지 못했습니다. 나를 위해 일한 것은 PDF 파일을 자체 디렉토리 로 옮기려고했습니다 . 파일이 다른 프로그램에서 열려 있으면 이것이 실패하고 이동이 성공하면 별도의 디렉토리로 이동했을 때와 같이 복원 작업이 필요하지 않습니다. 다른 사람의 특정 사용 사례에 유용 할 수 있도록 기본 솔루션을 게시하고 싶습니다.
string str_path_and_name = str_path + '\\' + str_filename;
FileInfo fInfo = new FileInfo(str_path_and_name);
bool open_elsewhere = false;
try
{
fInfo.MoveTo(str_path_and_name);
}
catch (Exception ex)
{
open_elsewhere = true;
}
if (open_elsewhere)
{
//handle case
}
이것이 WTF 반사를 유발하는지 확인하고 싶습니다. 콘솔 앱에서 PDF 문서를 생성 한 다음 실행하는 프로세스가 있습니다. 그러나 사용자가 프로세스를 여러 번 실행하여 이전에 생성 된 파일을 먼저 닫지 않고 동일한 파일을 생성하면 응용 프로그램에서 예외가 발생하고 사망하는 약점을 처리했습니다. 파일 이름은 판매 견적 번호를 기반으로하기 때문에 다소 자주 발생했습니다.
그렇게 부끄러운 방식으로 실패하기보다는 자동 증분 파일 버전 관리에 의존하기로 결정했습니다.
private static string WriteFileToDisk(byte[] data, string fileName, int version = 0)
{
try
{
var versionExtension = version > 0 ? $"_{version:000}" : string.Empty;
var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"{fileName}{versionExtension}.pdf");
using (var writer = new FileStream(filePath, FileMode.Create))
{
writer.Write(data, 0, data.Length);
}
return filePath;
}
catch (IOException)
{
return WriteFileToDisk(data, fileName, ++version);
}
}
아마도 catch
올바른 IOException을 잡을 수 있도록 블록에 약간의주의를 기울일 수 있습니다 . 이 파일들은 어쨌든 임시 파일이기 때문에 시작시 앱 저장소를 지울 것입니다.
파일이 사용 중인지 여부를 확인하는 OP의 질문 범위를 넘어서지 만 실제로 여기에 도착했을 때 해결하려는 문제 였으므로 다른 사람에게 유용 할 것입니다.
이 같은 도움이 되겠습니까?
var fileWasWrittenSuccessfully = false;
while (fileWasWrittenSuccessfully == false)
{
try
{
lock (new Object())
{
using (StreamWriter streamWriter = new StreamWriter(filepath.txt"), true))
{
streamWriter.WriteLine("text");
}
}
fileWasWrittenSuccessfully = true;
}
catch (Exception)
{
}
}
임시 디렉토리로 파일을 이동 / 복사하십시오. 가능하면 잠금이 없으며 잠금을받지 않고 임시 디렉토리에서 안전하게 작업 할 수 있습니다. 그렇지 않으면 x 초 안에 다시 이동하십시오.
이 해결 방법을 사용하지만 IsFileLocked 함수로 파일 잠금을 확인할 때와 파일을 열 때 사이의 시간 간격이 있습니다. 이 시간 범위에서 다른 스레드가 파일을 열 수 있으므로 IOException이 발생합니다.
그래서이 코드를 추가했습니다. 제 경우에는 XDocument를로드하고 싶습니다.
XDocument xDoc = null;
while (xDoc == null)
{
while (IsFileBeingUsed(_interactionXMLPath))
{
Logger.WriteMessage(Logger.LogPrioritet.Warning, "Deserialize can not open XML file. is being used by another process. wait...");
Thread.Sleep(100);
}
try
{
xDoc = XDocument.Load(_interactionXMLPath);
}
catch
{
Logger.WriteMessage(Logger.LogPrioritet.Error, "Load working!!!!!");
}
}
어떻게 생각해? 내가 바꿀 수 있습니까? 어쩌면 나는 IsFileBeingUsed 함수를 전혀 사용할 필요가 없었습니까?
감사