2011-01-13 10 views
25

Ich habe ein Programm, das als Windows-Dienst ausgeführt wird; Es verarbeitet Dateien in einem bestimmten Ordner. Da es sich um einen Dienst handelt, überwacht es ständig einen Ordner auf neue Dateien, die hinzugefügt wurden. Ein Teil der Aufgabe des Programms besteht darin, Vergleiche von Dateien im Zielordner durchzuführen und nicht übereinstimmende Dateien zu markieren. Was ich tun möchte, ist in der Lage zu erkennen, ob ein Kopiervorgang ausgeführt wird und wann er abgeschlossen ist, so dass eine Datei nicht vorzeitig markiert wird, wenn die entsprechende Datei noch nicht in den Zielordner kopiert wurde.C# - Warte auf einen Kopiervorgang

Was ich dachte, war die Verwendung des FileSystemWatcher, um den Zielordner zu sehen und zu sehen, ob ein Kopiervorgang auftritt. Wenn dies der Fall ist, lege ich den Hauptthread meines Programms in den Ruhezustand, bis der Kopiervorgang abgeschlossen ist, und führe die Operation für den Ordner wie üblich durch. Ich wollte nur einen Einblick in diesen Ansatz bekommen und sehen, ob er gültig ist. Wenn jemand anderes andere einzigartige Ansätze für dieses Problem hat, wäre dies sehr zu begrüßen.

UPDATE:

Vielen Dank für Ihre Vorschläge

UPDATE 2:

ich für die Verwirrung entschuldigen, wenn ich Zielverzeichnis sage, meine ich den Quellordner alle Dateien enthält, möchte ich herstellen. Ein Teil der Funktion meines Programms besteht darin, die Verzeichnisstruktur des Quellverzeichnisses in ein Zielverzeichnis zu kopieren und alle gültigen Dateien in dieses Zielverzeichnis zu kopieren, wobei die Verzeichnisstruktur des ursprünglichen Quellverzeichnisses erhalten bleibt, dh ein Benutzer kann Ordner kopieren, die Dateien enthalten in das Quellverzeichnis. Ich möchte Fehler verhindern, indem ich sicherstelle, dass, wenn ein neuer Satz von Ordnern mit mehr Unterordnern und Dateien zum Verarbeiten in das Quellverzeichnis kopiert wird, mein Programm erst nach dem Kopiervorgang im Zielverzeichnis arbeitet.

+0

+1. Das ist eine sehr gute Frage. Ich muss mir noch einen Ansatz ausdenken, der sich nicht wie ein Hack anfühlt. – David

+1

Diese Frage ist ähnlich und hat einige gute Antworten: http://stackoverflow.com/questions/30074/monitoring-files-how-to-know-when-a-file-is-complete – mfdoran

Antwort

2

Was Sie suchen, ist ein typisches producer/consumer Szenario. Was Sie tun müssen, ist in Abschnitt auf dieser page beschrieben. Dadurch können Sie Multi Threading verwenden (um einen Hintergrundarbeiter zu überspannen), um Dateien nicht zu blockieren, damit der Hauptdienst-Thread keine Systemereignisse abhören kann. & Sie können dort sinnvollere Aufgaben ausführen - z. B. nach neuen Dateien suchen & die Warteschlange aktualisieren . So on main thread do check for new files auf background threads perform the actual coping task. Aus persönlicher Erfahrung (haben diese Aufgaben implementiert) gibt es nicht viel Leistungsgewinn von diesem Ansatz, es sei denn, Sie laufen auf mehreren CPU-Maschine, aber der Prozess ist sehr sauber & glatt + der Code ist logisch gut getrennt.

Kurz gesagt, was Sie tun müssen, ist ein Objekt, wie folgt aus:

public class File 
{ 
    public string FullPath {get; internal set;} 
    public bool CopyInProgress {get; set;} // property to make sure 
    // .. other properties if desired 
} 

Dann das Tutorial folgende oben Ausgangs eine Sperre auf dem File-Objekt & die Warteschlange, sie zu aktualisieren & es kopieren. Mit diesem Ansatz können Sie diese type approaches verwenden, anstatt die Dateikopie ständig zu überwachen. Der wichtige Punkt hier zu realisieren ist, dass Ihr Dienst nur eine Instanz des Dateiobjekts pro tatsächliche physische Datei hat - stellen Sie sicher, dass Sie (1) Ihre Warteschlange beim Hinzufügen sperren & Entfernen & (2) das tatsächliche Dateiobjekt beim Initialisieren eines Updates sperren .

EDIT: Above, wo ich sagen: „Es ist nicht zu viel Performance-Gewinn von diesem Ansatz, es sei denn:“ Ich refere, wenn Sie diesen Ansatz in einem einzigen Thread tun zu vergleichen, um @ Jasons diesen Ansatz darauf hindeutet, muss deutlich schneller aufgrund @ Jasons Lösung führt sehr teure IO-Operationen aus, die in den meisten Fällen fehlschlagen. Dies habe ich nicht getestet, aber ich bin mir ziemlich sicher, da mein Ansatz keine IO-Operationen erfordert (nur einmal öffnen), Stream (nur einmal) & Datei schließen (nur einmal). @Jason Ansatz schlägt mehrere offene, offene, offene, offene Operationen, die alle ausfallen mit Ausnahme der letzten.

+0

Dies ist sehr viel, was ich erreichen möchte, muss ich verhindern, dass mein Programm nichts mit den Dateien/Ordnern im Quellverzeichnis tun, während eine Kopie in ist Fortschritt. – kingrichard2005

10

Yup, verwenden Sie eine FileSystemWatcher, aber suchen Sie nicht nach dem erstellten Ereignis, sondern nach dem geänderten Ereignis. Versuchen Sie nach jedem Trigger, die Datei zu öffnen. So etwas wie das:

var watcher = new FileSystemWatcher(path, filter); 
watcher.Changed += (sender, e) => { 
    FileStream file = null; 
    try { 
     Thread.Sleep(100); // hack for timing issues 
     file = File.Open(
      e.FullPath, 
      FileMode.Open, 
      FileAccess.Read, 
      FileShare.Read 
     ); 
    } 
    catch(IOException) { 
     // we couldn't open the file 
     // this is probably because the copy operation is not done 
     // just swallow the exception 
     return; 
    } 

    // now we have a handle to the file 
}; 

Dies ist das Beste, was Sie tun können, leider. Es gibt keinen sauberen Weg zu wissen, dass die Datei für Sie bereit ist.

+0

Das ist so ziemlich, wie ich ' habe es auch behandelt. Es fühlt sich immer "Hack-ish" an, aber +1, weil ich nichts besseres habe. – David

+1

@ivo s: Ineffizient? Teuer? Wen interessiert das? Es passiert auf einem Hintergrundthread und ist so unglaublich unwahrscheinlich, ein Flaschenhals zu sein. Pfui. Außerdem machen Sie eine Annahme, dass er die Kontrolle über den Kopierprozess hat. Beachten Sie, dass dies ursprünglich nicht in seiner Frage war. – jason

+2

Wenn Sie sich abenteuerlustig fühlen, können Sie den Aufwand vermeiden, die Ausnahmen abfangen zu müssen, indem Sie einen p/invoke-Aufruf verwenden, um zu versuchen, die Datei anstelle des .NET-Wrapper-Aufrufs zu öffnen. Der zugrundeliegende WIN32-Aufruf gibt einfach ein NULL-Handle für Fehler zurück (Sie sollten den Aufruf mithilfe von SafeFileHandle von .Net für eine ordnungsgemäße Verarbeitung marshalieren). Wenn der Aufruf schließlich erfolgreich ist, können Sie ein FileStream-Objekt aus dem SafeFileHandle erstellen. Schreiben Sie Ihren eigenen Wrapper für den Aufruf p/invoke, um einen FileStream zurückzugeben (oder null bei einem Fehler), und vermeiden Sie das try/catch. –

2

Ein Ansatz besteht darin, zu versuchen, die Datei zu öffnen und festzustellen, ob Sie einen Fehler erhalten. Die Datei wird gesperrt, wenn sie kopiert wird. Dadurch wird die Datei im Shared-Modus öffnen, so dass es mit einem bereits geöffneten Schreibsperre auf die Datei in Konflikt stehen:

using(System.IO.File.Open("file", FileMode.Open,FileAccess.Read, FileShare.Read)) {} 

Ein weiterer Grund ist die Dateigröße zu überprüfen. Es würde sich im Laufe der Zeit ändern, wenn die Datei kopiert wird.

Es ist auch möglich, eine Liste aller Anwendungen zu erhalten, die eine bestimmte Datei geöffnet hat, aber ich kenne die API dafür nicht.

1

Ich weiß, das ist eine alte Frage, aber hier ist eine Antwort, die ich nach der Suche nach einer Antwort auf genau dieses Problem gemacht habe. Dies musste stark optimiert werden, um etwas von der Eigenständigkeit von dem, an dem ich arbeitete, zu entfernen, so dass dies möglicherweise nicht direkt kompiliert wird, aber es wird Ihnen eine Idee geben. Das funktioniert gut für mich:



void BlockingFileCopySync(FileInfo original, FileInfo copyPath) 
{ 
    bool ready = false; 

    FileSystemWatcher watcher = new FileSystemWatcher(); 
    watcher.NotifyFilter = NotifyFilters.LastWrite; 
    watcher.Path = copyPath.Directory.FullName; 
    watcher.Filter = "*" + copyPath.Extension; 
    watcher.EnableRaisingEvents = true; 

    bool fileReady = false; 
    bool firsttime = true; 
    DateTime previousLastWriteTime = new DateTime(); 

    // modify this as you think you need to... 
    int waitTimeMs = 100; 

    watcher.Changed += (sender, e) => 
    { 
     // Get the time the file was modified 
     // Check it again in 100 ms 
     // When it has gone a while without modification, it's done. 
     while (!fileReady) 
     { 
      // We need to initialize for the "first time", 
      // ie. when the file was just created. 
      // (Really, this could probably be initialized off the 
      // time of the copy now that I'm thinking of it.) 
      if (firsttime) 
      { 
       previousLastWriteTime = System.IO.File.GetLastWriteTime(copyPath.FullName); 
       firsttime = false; 
       System.Threading.Thread.Sleep(waitTimeMs); 
       continue; 
      } 

      DateTime currentLastWriteTime = System.IO.File.GetLastWriteTime(copyPath.FullName); 

      bool fileModified = (currentLastWriteTime != previousLastWriteTime); 

      if (fileModified) 
      { 
       previousLastWriteTime = currentLastWriteTime; 
       System.Threading.Thread.Sleep(waitTimeMs); 
       continue; 
      } 
      else 
      { 
       fileReady = true; 
       break; 
      } 
     } 
    }; 

    System.IO.File.Copy(original.FullName, copyPath.FullName, true); 

    // This guy here chills out until the filesystemwatcher 
    // tells him the file isn't being writen to anymore. 
    while (!fileReady) 
    { 
     System.Threading.Thread.Sleep(waitTimeMs); 
    } 
} 

+0

Leider funktioniert diese Lösung nicht für mich. GetLastWriteTime wird nicht aktualisiert, während die Datei kopiert wird. Ich benutze Windows 7, vielleicht ist das Verhalten auf Windows Server anders, nicht sicher. – KyleLib

+0

@KyleLib - Wirklich? Interessant, ich habe auch Windows 7 benutzt. Neugierig. Ich bin mir nicht sicher, was ich davon halten soll. Ich muss das vielleicht wieder durchbrechen und etwas durcheinander bringen. – trycatch