Est-il un moyen de vérifier si un fichier est en cours d'utilisation?
j'écris un programme en C# qui a besoin d'accéder de façon répétée au fichier image 1. La plupart du temps, cela fonctionne, mais si mon ordinateur tourne rapidement, il va essayer d'accéder au fichier avant qu'il n'ait été sauvegardé dans le système de fichiers et de lancer une erreur: "fichier utilisé par un autre processus" ".
je voudrais trouver un moyen de contourner cela, mais tout mon Googling a seulement donné la création de contrôles en utilisant la manipulation d'exception. C'est contre ma religion, j'ai donc été vous demandez-vous si quelqu'un a une meilleure façon de le faire?
16 réponses
mise à jour de la NOTE sur cette solution : le contrôle avec FileAccess.ReadWrite
échouera pour les fichiers en lecture seule de sorte que la solution a été modifiée pour vérifier avec FileAccess.Read
. Bien que cette solution fonctionne parce qu'essayer de vérifier avec FileAccess.Read
échouera si le fichier a une écriture ou une lecture lock sur elle, cependant, cette solution ne fonctionnera pas si le fichier n'a pas une écriture ou une lecture lock sur elle, i.e. il a été ouvert (pour la lecture ou l'écriture) avec FileShare.Lire ou FileShare.Écrire accès.
ORIGINAL: J'ai utilisé ce code ces dernières années, et je n'ai eu aucun problème avec lui.
comprenez votre hésitation à utiliser des exceptions, mais vous ne pouvez pas les éviter tout le temps:
protected virtual bool IsFileLocked(FileInfo file)
{
FileStream stream = null;
try
{
stream = file.Open(FileMode.Open, FileAccess.Read, FileShare.None);
}
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;
}
finally
{
if (stream != null)
stream.Close();
}
//file is not locked
return false;
}
vous pouvez souffrir d'une condition de course de thread sur ce qui il y a des exemples documentés de ceci étant utilisé comme une vulnérabilité de sécurité. Si vous vérifiez que le fichier est disponible, mais ensuite essayer et l'utiliser vous pourriez jeter à ce point, qu'un utilisateur malveillant pourrait utiliser pour forcer et exploiter dans votre code.
votre meilleur pari est un try catch / finally qui essaie d'obtenir la poignée de fichier.
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.
}
Utiliser pour vérifier si un fichier est verrouillé:
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;
}
}
pour des raisons de performance, je vous recommande de lire le contenu du fichier dans la même opération. Voici quelques exemples:
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;
}
essayez vous-même:
byte[] output1 = Helper.ReadFileBytes(@"c:\temp\test.txt");
string output2 = Helper.ReadFileTextWithEncoding(@"c:\temp\test.txt");
string output3 = Helper.ReadFileTextNoEncoding(@"c:\temp\test.txt");
peut-être pourriez-vous utiliser un FileSystemWatcher et regarder pour L'événement changé.
Je ne l'ai pas utilisé moi-même, mais ça pourrait valoir le coup. Si le filesystemwatcher s'avère à être un peu lourd pour ce cas, je pencherais pour le try/catch/sommeil boucle.
le seul moyen que je connaisse est D'utiliser L'API de verrouillage exclusive Win32 qui n'est pas trop rapide, mais des exemples existent.
la plupart des gens, pour une solution simple à cela, simplement pour essayer/attraper/boucles de sommeil.
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");
Espérons que cette aide!
utilisez juste l'exception comme prévu. Acceptez que le fichier soit utilisé et réessayez, plusieurs fois jusqu'à ce que votre action soit terminée. C'est aussi le plus efficace parce que vous ne perdez pas de cycles vérifiant l'état avant d'agir.
Utiliser la fonction ci-dessous, par exemple
TimeoutFileAction(() => { System.IO.File.etc...; return null; } );
méthode réutilisable qui s'éteint après 2 secondes
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);
}
Vous pouvez revenir à une tâche qui vous donne un flux dès qu'il devient disponible. C'est une solution simplifiée, mais c'est un bon point de départ. C'est thread-safe.
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();
}
}
Vous pouvez utiliser ce flux comme d'habitude:
using (var stream = await FileStreamGetter.GetStreamAsync())
{
Console.WriteLine(stream.Length);
}
les réponses acceptées ci-dessus souffrent d'un problème où si le dossier a été ouvert pour l'écriture avec un FileShare.Le mode Lecture ou si le fichier a un attribut Lecture Seule le code ne fonctionnera pas. Cette solution modifiée fonctionne de la façon la plus fiable, avec deux choses à garder à l'esprit (comme vrai pour la solution acceptée aussi):
- il ne fonctionnera pas pour les fichiers qui ont été ouverts avec un mode de partage d'écriture
- cela ne prend pas en compte les questions de filetage ainsi vous aurez besoin de verrouiller ou traiter les questions de filetage séparément.
en gardant à l'esprit ce qui précède, ceci vérifie si le fichier est soit verrouillé pour écriture ou verrouillé pour empêcher la lecture :
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;
}
voici un code qui, pour autant que je sache, fait la même chose que la réponse acceptée, mais avec moins de code:
public static bool IsFileLocked(string file)
{
try
{
using (var stream = File.OpenRead(file))
return false;
}
catch (IOException)
{
return true;
}
}
Cependant, je pense qu'il est plus robuste le faire de la manière suivante:
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);
}
vous pouvez utiliser Ma bibliothèque pour accéder à des fichiers à partir de plusieurs applications.
vous pouvez l'installer à partir de nuget: Install-Package Xabe.FileLock
si vous voulez plus d'informations à propos de it check https://github.com/tomaszzmuda/Xabe.FileLock
ILock fileLock = new FileLock(file);
if(fileLock.Acquire(TimeSpan.FromSeconds(15), true))
{
using(fileLock)
{
// file operations here
}
}
fileLock.La méthode d'acquisition retournera true seulement si peut verrouiller le fichier exclusif pour cet objet. Mais l'application qui uploader le fichier doit le faire dans la serrure du fichier trop. Si l'objet est inaccessible, metod retourne false.
d'après mon expérience, vous voulez généralement faire cela, puis "protéger" vos fichiers pour faire quelque chose de fantaisie et ensuite utiliser les fichiers "protégés". Si vous avez juste un fichier que vous voulez utiliser comme ceci, vous pouvez utiliser le truc qui est expliqué dans la réponse par Jeremy Thompson. Cependant, si vous tentez de le faire sur un grand nombre de fichiers (par exemple, lorsque vous écrivez un programme d'installation), vous risquez de vous blesser.
une façon très élégante de résoudre ce problème est d'utiliser le fait que votre système de fichiers ne vous permettra pas de changer le nom d'un dossier si l'un des fichiers est utilisé. Gardez le dossier dans le même système de fichiers et ça fonctionnera comme un charme.
notez que vous devez être conscient de la manière évidente, cela peut être exploité. Après tout, les dossiers ne seront pas verrouillés. De plus, sachez qu'il y a d'autres raisons qui peuvent entraîner l'échec de votre opération Move
. De toute évidence, une bonne gestion des erreurs (MSDN) peut aider ici.
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);
}
pour les dossiers individuels, Je m'en tiendrais à la suggestion de fermeture Postée par Jeremy Thompson.
mis à part le travail 3-liners et juste pour référence: si vous voulez le full blown information - Il ya un petit projet sur Microsoft Dev Center:
https://code.msdn.microsoft.com/windowsapps/How-to-know-the-process-704839f4
De l'Introduction:
l'exemple de code C# développé dans le cadre de .NET 4.0 pourrait aider à trouver ce qui est le processus qui est d'avoir un verrou sur un fichier. RmStartSession fonction qui est incluse dans rstrtmgr.dll a été utilisé pour créer une session de gestionnaire de redémarrage et selon le retour résultat une nouvelle instance de L'objet Win32Exception est créée. Après enregistrer les ressources dans une session de gestionnaire de redémarrage via RmRegisterRescources , de RmGetList la fonction est appelée pour vérifier quelles sont les applications sont utilisation d'un fichier particulier en énumérant le tableau RM_PROCESS_INFO .
il fonctionne en se connectant à la"session de gestionnaire de redémarrage".
le Gestionnaire de redémarrage Utilise la liste des ressources enregistrées avec la session pour déterminer les applications et les services qui doivent être arrêtés et redémarrés. Les ressources peuvent être identifiées par des noms de fichier, des noms courts de service, ou RM_UNIQUE_PROCESS structures qui décrivent des applications en cours d'exécution.
Il pourrait être un peu overengineered pour vos besoins particuliers... Mais si c'est ce que vous voulez, allez-y et saisir le vs-projet.
je suis intéressé de voir si cela déclenche des réflexes WTF. J'ai un processus qui crée et lance ensuite un document PDF à partir d'une application console. Cependant, je faisais face à une fragilité où si l'utilisateur devait exécuter le processus plusieurs fois, générant le même fichier sans fermer d'abord le fichier généré précédemment, l'application lancerait une exception et mourrait. Cela s'est produit assez fréquemment parce que les noms de fichiers sont basés sur les numéros de devis de vente.
Plutôt que d'échouer d'une manière aussi ingrate, j'ai décidé de m'appuyer sur le versioning de fichier auto-incrémenté:
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);
}
}
probablement un peu plus de soin peut être donné au bloc catch
pour s'assurer que j'attrape le bon IOException(s). Je vais probablement aussi effacer le stockage de l'application au démarrage puisque ces fichiers sont destinés à être temporaires de toute façon.
je me rends compte que cela va au-delà de la question de L'OP de simplement vérifier si le fichier est en utilisation mais c'était effectivement le problème que je cherchais à résoudre lorsque je suis arrivé ici, donc peut-être qu'il sera utile à quelqu'un d'autre.
Essayez et déplacer/copier le fichier dans un dossier temp dir. Si vous le pouvez, il n'a pas de serrure et vous pouvez travailler en toute sécurité dans le DIR temporaire sans obtenir des serrures. Sinon, essaie de le bouger en x secondes.
j'utilise cette solution de contournement, mais j'ai un intervalle de temps entre le moment où je vérifie le verrouillage du fichier avec la fonction IsFileLocked et le moment où j'ouvre le fichier. Dans ce laps de temps, un autre fil peut ouvrir le fichier, donc je vais obtenir IOException.
donc, j'ai ajouté un code supplémentaire pour ça. Dans mon cas, je veux load 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!!!!!");
}
}
Qu'en pensez-vous? Puis-je changer quelque chose? Peut-être que je n'ai pas eu à utiliser la fonction IsFileBeingUsed du tout?
Merci