Attendre que le fichier soit déverrouillé in.NET

Quelle est la façon la plus simple de bloquer un thread jusqu'à ce qu'un fichier ait été déverrouillé et soit accessible pour lire et renommer? Par exemple, y a-t-il un WaitOnFile() quelque part dans le Framework .NET?

j'ai un service qui utilise un système de fichiers Watcher pour rechercher des fichiers qui doivent être transmis à un site FTP, mais le fichier créé se déclenche avant que l'autre processus ait fini d'écrire le fichier.

l'idéal solution aurait une période de temps d'arrêt de sorte que le fil ne pend pas éternellement avant d'abandonner.

Edit: après avoir essayé quelques-unes des solutions ci-dessous, j'ai fini par changer le système de sorte que tous les fichiers ont écrit à Path.GetTempFileName() , puis effectué un File.Move() à l'emplacement final. Dès que l'événement FileSystemWatcher a été déclenché, le fichier était déjà complet.

83
demandé sur Peter Mortensen 2008-09-09 01:33:09

15 réponses

C'est la réponse que j'ai donnée à une question connexe :

    /// <summary>
    /// Blocks until the file is not locked any more.
    /// </summary>
    /// <param name="fullPath"></param>
    bool WaitForFile(string fullPath)
    {
        int numTries = 0;
        while (true)
        {
            ++numTries;
            try
            {
                // Attempt to open the file exclusively.
                using (FileStream fs = new FileStream(fullPath,
                    FileMode.Open, FileAccess.ReadWrite, 
                    FileShare.None, 100))
                {
                    fs.ReadByte();

                    // If we got this far the file is ready
                    break;
                }
            }
            catch (Exception ex)
            {
                Log.LogWarning(
                   "WaitForFile {0} failed to get an exclusive lock: {1}", 
                    fullPath, ex.ToString());

                if (numTries > 10)
                {
                    Log.LogWarning(
                        "WaitForFile {0} giving up after 10 tries", 
                        fullPath);
                    return false;
                }

                // Wait for the lock to be released
                System.Threading.Thread.Sleep(500);
            }
        }

        Log.LogTrace("WaitForFile {0} returning true after {1} tries",
            fullPath, numTries);
        return true;
    }
36
répondu Eric Z Beard 2017-05-23 11:54:31

à partir de la réponse D'Eric, j'ai inclus quelques améliorations pour rendre le code beaucoup plus compact et réutilisable. J'espère que c'est utile.

FileStream WaitForFile (string fullPath, FileMode mode, FileAccess access, FileShare share)
{
    for (int numTries = 0; numTries < 10; numTries++) {
        FileStream fs = null;
        try {
            fs = new FileStream (fullPath, mode, access, share);
            return fs;
        }
        catch (IOException) {
            if (fs != null) {
                fs.Dispose ();
            }
            Thread.Sleep (50);
        }
    }

    return null;
}
55
répondu mafu 2017-03-06 18:39:48

voici un code générique pour le faire, indépendant de l'opération de fichier elle-même. Voici un exemple d'utilisation:

WrapSharingViolations(() => File.Delete(myFile));

ou

WrapSharingViolations(() => File.Copy(mySourceFile, myDestFile));

Vous pouvez également définir le nombre de tentatives, et le temps d'attente entre deux tentatives.

Note: malheureusement, L'erreur Win32 sous-jacente (ERROR_SHARING_VIOLATION) n'est pas exposée avec .NET, donc j'ai ajouté une petite fonction de hack ( IsSharingViolation ) basée sur la réflexion mécanismes pour vérifier cela.

    /// <summary>
    /// Wraps sharing violations that could occur on a file IO operation.
    /// </summary>
    /// <param name="action">The action to execute. May not be null.</param>
    public static void WrapSharingViolations(WrapSharingViolationsCallback action)
    {
        WrapSharingViolations(action, null, 10, 100);
    }

    /// <summary>
    /// Wraps sharing violations that could occur on a file IO operation.
    /// </summary>
    /// <param name="action">The action to execute. May not be null.</param>
    /// <param name="exceptionsCallback">The exceptions callback. May be null.</param>
    /// <param name="retryCount">The retry count.</param>
    /// <param name="waitTime">The wait time in milliseconds.</param>
    public static void WrapSharingViolations(WrapSharingViolationsCallback action, WrapSharingViolationsExceptionsCallback exceptionsCallback, int retryCount, int waitTime)
    {
        if (action == null)
            throw new ArgumentNullException("action");

        for (int i = 0; i < retryCount; i++)
        {
            try
            {
                action();
                return;
            }
            catch (IOException ioe)
            {
                if ((IsSharingViolation(ioe)) && (i < (retryCount - 1)))
                {
                    bool wait = true;
                    if (exceptionsCallback != null)
                    {
                        wait = exceptionsCallback(ioe, i, retryCount, waitTime);
                    }
                    if (wait)
                    {
                        System.Threading.Thread.Sleep(waitTime);
                    }
                }
                else
                {
                    throw;
                }
            }
        }
    }

    /// <summary>
    /// Defines a sharing violation wrapper delegate.
    /// </summary>
    public delegate void WrapSharingViolationsCallback();

    /// <summary>
    /// Defines a sharing violation wrapper delegate for handling exception.
    /// </summary>
    public delegate bool WrapSharingViolationsExceptionsCallback(IOException ioe, int retry, int retryCount, int waitTime);

    /// <summary>
    /// Determines whether the specified exception is a sharing violation exception.
    /// </summary>
    /// <param name="exception">The exception. May not be null.</param>
    /// <returns>
    ///     <c>true</c> if the specified exception is a sharing violation exception; otherwise, <c>false</c>.
    /// </returns>
    public static bool IsSharingViolation(IOException exception)
    {
        if (exception == null)
            throw new ArgumentNullException("exception");

        int hr = GetHResult(exception, 0);
        return (hr == -2147024864); // 0x80070020 ERROR_SHARING_VIOLATION

    }

    /// <summary>
    /// Gets the HRESULT of the specified exception.
    /// </summary>
    /// <param name="exception">The exception to test. May not be null.</param>
    /// <param name="defaultValue">The default value in case of an error.</param>
    /// <returns>The HRESULT value.</returns>
    public static int GetHResult(IOException exception, int defaultValue)
    {
        if (exception == null)
            throw new ArgumentNullException("exception");

        try
        {
            const string name = "HResult";
            PropertyInfo pi = exception.GetType().GetProperty(name, BindingFlags.NonPublic | BindingFlags.Instance); // CLR2
            if (pi == null)
            {
                pi = exception.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance); // CLR4
            }
            if (pi != null)
                return (int)pi.GetValue(exception, null);
        }
        catch
        {
        }
        return defaultValue;
    }
15
répondu Simon Mourier 2013-01-16 18:06:31

j'ai organisé un cours d'aide pour ce genre de choses. Cela fonctionnera si vous avez le contrôle sur tout ce qui accéderait au fichier. Si vous attendez de la contention d'un tas d'autres choses, alors c'est assez inutile.

using System;
using System.IO;
using System.Threading;

/// <summary>
/// This is a wrapper aroung a FileStream.  While it is not a Stream itself, it can be cast to
/// one (keep in mind that this might throw an exception).
/// </summary>
public class SafeFileStream: IDisposable
{
    #region Private Members
    private Mutex m_mutex;
    private Stream m_stream;
    private string m_path;
    private FileMode m_fileMode;
    private FileAccess m_fileAccess;
    private FileShare m_fileShare;
    #endregion//Private Members

    #region Constructors
    public SafeFileStream(string path, FileMode mode, FileAccess access, FileShare share)
    {
        m_mutex = new Mutex(false, String.Format("Global\{0}", path.Replace('\', '/')));
        m_path = path;
        m_fileMode = mode;
        m_fileAccess = access;
        m_fileShare = share;
    }
    #endregion//Constructors

    #region Properties
    public Stream UnderlyingStream
    {
        get
        {
            if (!IsOpen)
                throw new InvalidOperationException("The underlying stream does not exist - try opening this stream.");
            return m_stream;
        }
    }

    public bool IsOpen
    {
        get { return m_stream != null; }
    }
    #endregion//Properties

    #region Functions
    /// <summary>
    /// Opens the stream when it is not locked.  If the file is locked, then
    /// </summary>
    public void Open()
    {
        if (m_stream != null)
            throw new InvalidOperationException(SafeFileResources.FileOpenExceptionMessage);
        m_mutex.WaitOne();
        m_stream = File.Open(m_path, m_fileMode, m_fileAccess, m_fileShare);
    }

    public bool TryOpen(TimeSpan span)
    {
        if (m_stream != null)
            throw new InvalidOperationException(SafeFileResources.FileOpenExceptionMessage);
        if (m_mutex.WaitOne(span))
        {
            m_stream = File.Open(m_path, m_fileMode, m_fileAccess, m_fileShare);
            return true;
        }
        else
            return false;
    }

    public void Close()
    {
        if (m_stream != null)
        {
            m_stream.Close();
            m_stream = null;
            m_mutex.ReleaseMutex();
        }
    }

    public void Dispose()
    {
        Close();
        GC.SuppressFinalize(this);
    }

    public static explicit operator Stream(SafeFileStream sfs)
    {
        return sfs.UnderlyingStream;
    }
    #endregion//Functions
}

Il fonctionne à l'aide d'un mutex nommé. Ceux qui souhaitent accéder au fichier tentative d'acquérir le contrôle de la mutex nommé, qui partage le nom du fichier (avec le '\'est transformé en'/'). Vous pouvez soit utiliser Open(), qui va décrocher jusqu'à ce que le mutex soit accessible ou vous pouvez utiliser TryOpen(TimeSpan), qui tente d'acquérir le mutex pour la durée donnée et retourne false si elle ne peut pas acquérir dans le laps de temps. Cela devrait probablement être utilisé à l'intérieur d'un bloc using, pour s'assurer que les verrous sont relâchés correctement, et le flux (si ouverte) seront éliminés correctement lorsque cet objet est supprimé.

j'ai fait un test rapide avec ~20 choses à faire différentes lit/écrit du dossier et n'a vu aucune corruption. Évidemment, il n'est pas très avancé, mais il devrait fonctionner pour la majorité des cas simples.

13
répondu user152791 2009-08-07 22:40:56

pour cette application particulière observer directement le fichier entraînera inévitablement un bogue difficile à tracer, surtout lorsque la taille du fichier augmente. Voici deux stratégies différentes qui fonctionneront.

  • Ftp deux fichiers mais un seul. Par exemple envoyer les fichiers importants.txt et important.terminer. Ne cherchez que le fichier de finition, mais traitez le txt.
  • FTP un fichier mais le renommer une fois fait. Par exemple envoyer importante.attendre et ont l'expéditeur renommer important.txt une fois terminé.

bonne chance!

5
répondu jason saldo 2008-09-09 00:20:33

l'une des techniques que j'ai utilisées il y a quelque temps était d'écrire ma propre fonction. Essentiellement attraper l'exception et réessayer en utilisant une minuterie que vous pouvez tirer pour une durée spécifiée. S'il y a un meilleur moyen, partagez.

4
répondu Gulzar Nazim 2008-09-08 21:37:55

de MSDN :

La OnCreated événement est déclenché dès un fichier est créé. Si un fichier est être copié ou transféré dans un regardé répertoire, le OnCreated événement seront soulevées immédiatement, suivies par un ou plusieurs OnChanged événements.

votre système de fichiers pourrait être modifié de sorte qu'il ne fasse pas sa lecture/renommer lors de l'événement "OnCreated", mais plutôt:

  1. S'étend sur un fil qui affiche l'état du fichier jusqu'à ce qu'il ne soit pas verrouillé (en utilisant un objet FileInfo)
  2. rappelle au service pour traiter le fichier dès qu'il détermine que le fichier n'est plus verrouillé et qu'il est prêt à partir
3
répondu Guy Starbuck 2008-09-08 21:48:11

dans la plupart des cas une approche simple comme @harpo suggéré fonctionnera. Vous pouvez développer un code plus sophistiqué en utilisant cette approche:

  • trouver tous les poignées ouvertes pour le fichier sélectionné en utilisant SystemHandleInformation\SystemInformation
  • sous-classe WaitHandle classe pour accéder à la poignée interne
  • pass a trouvé des poignées enveloppées dans du WaitHandle subclassé à WaitHandle.WaitAny method
2
répondu aku 2008-09-09 00:11:30

Annonce d'un processus de transfert de déclencher fichier SameNameASTrasferedFile.trg qui est créé après la transmission du fichier.

ensuite setup FileSystemWatcher qui va lancer l'événement seulement sur *.fichier trg.

2
répondu Rudi 2011-07-08 15:10:18

Je ne sais pas ce que vous utilisez pour déterminer le statut de verrouillage du fichier, mais quelque chose comme ça devrait le faire.

while (true)
{
    try {
        stream = File.Open( fileName, fileMode );
        break;
    }
    catch( FileIOException ) {

        // check whether it's a lock problem

        Thread.Sleep( 100 );
    }
}
1
répondu harpo 2008-09-08 21:42:27

une solution possible serait de combiner un système de fichiers avec un sondage,

obtenir notifié pour chaque changement sur un dossier, et lors de la réception de la notification vérifier si elle est verrouillé comme indiqué dans la réponse actuellement acceptée: https://stackoverflow.com/a/50800/6754146 Le code d'ouverture du filestream est copié à partir de la réponse et légèrement modifié:

public static void CheckFileLock(string directory, string filename, Func<Task> callBack)
{
    var watcher = new FileSystemWatcher(directory, filename);
    FileSystemEventHandler check = 
        async (sender, eArgs) =>
    {
        string fullPath = Path.Combine(directory, filename);
        try
        {
            // Attempt to open the file exclusively.
            using (FileStream fs = new FileStream(fullPath,
                    FileMode.Open, FileAccess.ReadWrite,
                    FileShare.None, 100))
            {
                fs.ReadByte();
                watcher.EnableRaisingEvents = false;
                // If we got this far the file is ready
            }
            watcher.Dispose();
            await callBack();
        }
        catch (IOException) { }
    };
    watcher.NotifyFilter = NotifyFilters.LastWrite;
    watcher.IncludeSubdirectories = false;
    watcher.EnableRaisingEvents = true;
    //Attach the checking to the changed method, 
    //on every change it gets checked once
    watcher.Changed += check;
    //Initially do a check for the case it is already released
    check(null, null);
}

Avec cette façon, vous pouvez Vérifier pour un fichier si son verrouillé et être averti quand il est fermé sur le rappel spécifié, de cette façon vous évitez les sondages trop agressifs et ne faire le travail quand il peut être effectivement fermé

0
répondu Florian K 2017-05-23 11:46:56

je le fais de la même façon que Gulzar, continue d'essayer avec une boucle.

en fait, je ne m'occupe même pas de l'observateur du système de fichiers. Polling un lecteur réseau pour de nouveaux fichiers une fois par minute est pas cher.

-1
répondu Jonathan Allen 2008-09-08 23:14:58

il suffit D'utiliser le modifié événement avec le NotifyFilter NotifyFilters.LastWrite :

var watcher = new FileSystemWatcher {
      Path = @"c:\temp\test",
      Filter = "*.xml",
      NotifyFilter = NotifyFilters.LastWrite
};
watcher.Changed += watcher_Changed; 
watcher.EnableRaisingEvents = true;
-1
répondu Bernhard Hochgatterer 2013-01-22 21:09:59

j'ai rencontré un problème similaire en ajoutant une pièce jointe outlook. "À l'aide" sauvé la journée.

string fileName = MessagingBLL.BuildPropertyAttachmentFileName(currProp);

                //create a temporary file to send as the attachment
                string pathString = Path.Combine(Path.GetTempPath(), fileName);

                //dirty trick to make sure locks are released on the file.
                using (System.IO.File.Create(pathString)) { }

                mailItem.Subject = MessagingBLL.PropertyAttachmentSubject;
                mailItem.Attachments.Add(pathString, Outlook.OlAttachmentType.olByValue, Type.Missing, Type.Missing);
-1
répondu Jahmal23 2014-03-04 16:58:04

Que pensez-vous de cette option:

private void WaitOnFile(string fileName)
{
    FileInfo fileInfo = new FileInfo(fileName);
    for (long size = -1; size != fileInfo.Length; fileInfo.Refresh())
    {
        size = fileInfo.Length;
        System.Threading.Thread.Sleep(1000);
    }
}

bien sûr, si le fichier est pré-alloué sur le create, vous obtiendrez un faux positif.

-3
répondu Ralph Shillington 2009-07-23 12:56:35