Frage

Was ist die einfachste Möglichkeit, einen Thread zu blockieren, bis eine Datei entsperrt wurde und zum Lesen und Umbenennen zugänglich ist?Gibt es beispielsweise irgendwo im .NET Framework eine WaitOnFile()?

Ich habe einen Dienst, der einen FileSystemWatcher verwendet, um nach Dateien zu suchen, die an eine FTP-Site übertragen werden sollen, aber der Datei erstellt Das Ereignis wird ausgelöst, bevor der andere Prozess das Schreiben der Datei abgeschlossen hat.

Die ideale Lösung wäre eine Zeitüberschreitung, damit der Thread nicht ewig hängt, bevor er aufgibt.

Bearbeiten:Nachdem ich einige der folgenden Lösungen ausprobiert hatte, änderte ich schließlich die System damit alle Dateien geschrieben werden Path.GetTempFileName(), dann durchgeführt a File.Move() zum endgültigen Standort.Sobald FileSystemWatcher Das Ereignis wurde ausgelöst, die Datei war bereits vollständig.

War es hilfreich?

Lösung

Dies war die Antwort, die ich auf a gegeben habe verwandte Frage:

    /// <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;
    }

Andere Tipps

Ausgehend von Erics Antwort habe ich einige Verbesserungen vorgenommen, um den Code wesentlich kompakter und wiederverwendbar zu machen.Hoffe, es ist nützlich.

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;
}

Hier ist ein generischer Code, um dies zu tun, unabhängig von der Dateioperation selbst.Dies ist ein Beispiel für die Verwendung:

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

oder

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

Sie können auch die Anzahl der Wiederholungen und die Wartezeit zwischen Wiederholungen definieren.

NOTIZ:Leider wird der zugrunde liegende Win32-Fehler (ERROR_SHARING_VIOLATION) bei .NET nicht angezeigt, daher habe ich eine kleine Hack-Funktion hinzugefügt (IsSharingViolation) basierend auf Reflexionsmechanismen, um dies zu überprüfen.

    /// <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;
    }

Für solche Dinge habe ich eine Hilfsklasse zusammengestellt.Es funktioniert, wenn Sie die Kontrolle über alles haben, was auf die Datei zugreifen würde.Wenn Sie von einer Reihe anderer Dinge Streit erwarten, dann ist das ziemlich wertlos.

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
}

Es funktioniert mit einem benannten Mutex.Diejenigen, die auf die Datei zugreifen möchten, versuchen, die Kontrolle über den benannten Mutex zu erlangen, der den Namen der Datei teilt (wobei die „\“ in „/“ umgewandelt werden).Sie können entweder Open() verwenden, das anhält, bis auf den Mutex zugegriffen werden kann, oder Sie können TryOpen(TimeSpan) verwenden, das versucht, den Mutex für die angegebene Dauer abzurufen, und false zurückgibt, wenn es innerhalb der Zeitspanne nicht abgerufen werden kann.Dies sollte höchstwahrscheinlich innerhalb eines using-Blocks verwendet werden, um sicherzustellen, dass Sperren ordnungsgemäß freigegeben werden und der Stream (sofern geöffnet) ordnungsgemäß entsorgt wird, wenn dieses Objekt entsorgt wird.

Ich habe einen kurzen Test mit etwa 20 Dingen durchgeführt, um verschiedene Lese-/Schreibvorgänge für die Datei durchzuführen, und keine Beschädigung festgestellt.Offensichtlich ist es nicht sehr fortgeschritten, aber es sollte in den meisten einfachen Fällen funktionieren.

Bei dieser speziellen Anwendung führt die direkte Beobachtung der Datei unweigerlich zu einem schwer zu verfolgenden Fehler, insbesondere wenn die Dateigröße zunimmt.Hier sind zwei verschiedene Strategien, die funktionieren werden.

  • Zwei Dateien per FTP übertragen, aber nur eine ansehen.Senden Sie beispielsweise die Dateien important.txt und important.finish.Achten Sie nur auf die fertige Datei, verarbeiten Sie aber den Text.
  • FTP eine Datei, aber benennen Sie sie um, wenn Sie fertig sind.Senden Sie beispielsweise important.wait und lassen Sie es vom Absender nach Abschluss in important.txt umbenennen.

Viel Glück!

Eine der Techniken, die ich vor einiger Zeit verwendet habe, bestand darin, meine eigene Funktion zu schreiben.Fangen Sie grundsätzlich die Ausnahme ab und versuchen Sie es erneut, indem Sie einen Timer verwenden, den Sie für eine bestimmte Dauer auslösen können.Wenn es einen besseren Weg gibt, teilen Sie ihn bitte mit.

Aus MSDN:

Das aufgebaute Ereignis wird angesprochen, sobald eine Datei erstellt wird.Wenn eine Datei kopiert oder in ein beobachtetes Verzeichnis übertragen wird, wird das On -Created -Ereignis sofort angehoben, gefolgt von einem oder mehreren Onchanged -Ereignissen.

Ihr FileSystemWatcher könnte so geändert werden, dass er seinen Lese-/Umbenennungsvorgang nicht während des „OnCreated“-Ereignisses durchführt, sondern:

  1. Spanws einen Thread, der den Dateistatus abfragt, bis er nicht mehr gesperrt wird (mithilfe eines FileInfo-Objekts)
  2. Ruft den Dienst erneut auf, um die Datei zu verarbeiten, sobald festgestellt wird, dass die Datei nicht mehr gesperrt ist und einsatzbereit ist

In den meisten Fällen wird ein einfacher Ansatz wie der von @harpo vorgeschlagene funktionieren.Mit diesem Ansatz können Sie anspruchsvolleren Code entwickeln:

  • Suchen Sie mit SystemHandleInformation\SystemProcessInformation nach allen geöffneten Handles für die ausgewählte Datei
  • Unterklasse WaitHandle-Klasse, um Zugriff auf ihr internes Handle zu erhalten
  • Übergeben Sie gefundene Handles, die in das untergeordnete WaitHandle eingeschlossen sind, an die WaitHandle.WaitAny-Methode

AD -to -Transfer -Prozess -Triggerdatei SAMENAMESTRASFEREDFILE.TRG, die nach Abschluss der Dateiübertragung erstellt wird.

Richten Sie dann FileSystemWatcher ein, der das Ereignis nur für die *.trg-Datei auslöst.

Ich weiß nicht, was Sie verwenden, um den Sperrstatus der Datei zu ermitteln, aber so etwas sollte es tun.

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

        // check whether it's a lock problem

        Thread.Sleep( 100 );
    }
}

Eine mögliche Lösung wäre, einen Filesystemwatcher mit etwas Polling zu kombinieren,

Lassen Sie sich für jede Änderung einer Datei benachrichtigen und prüfen Sie, ob sie wie in der aktuell akzeptierten Antwort gesperrt ist: https://stackoverflow.com/a/50800/6754146Der Code zum Öffnen des Filestreams wurde aus der Antwort kopiert und leicht modifiziert:

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);
}

Auf diese Weise können Sie prüfen, ob eine Datei gesperrt ist, und über den angegebenen Rückruf benachrichtigt werden, wenn sie geschlossen wird. Auf diese Weise vermeiden Sie übermäßig aggressive Abfragen und führen die Arbeit nur aus, wenn sie möglicherweise tatsächlich geschlossen ist

Ich mache es genauso wie Gulzar, versuche es einfach weiter mit einer Schleife.

Tatsächlich beschäftige ich mich nicht einmal mit dem Dateisystem-Watcher.Es ist kostengünstig, einmal pro Minute ein Netzlaufwerk nach neuen Dateien abzufragen.

Nutzen Sie einfach die Geändert Ereignis mit dem NotifyFilter NotifyFilters.LastWrite:

var watcher = new FileSystemWatcher {
      Path = @"c:\temp\test",
      Filter = "*.xml",
      NotifyFilter = NotifyFilters.LastWrite
};
watcher.Changed += watcher_Changed; 
watcher.EnableRaisingEvents = true;

Beim Hinzufügen eines Outlook-Anhangs ist ein ähnliches Problem aufgetreten.„Benutzen“ hat den Tag gerettet.

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);

Wie wäre es als Option damit:

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);
    }
}

Wenn die Dateigröße beim Erstellen vorab zugewiesen ist, erhalten Sie natürlich ein falsches Positiv.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top