开发者

C# FileSystemWatcher, How to know file copied completely into the watch folder

I am developing a .net application, where I am using FileSystemWatcher class and attached its Created event on a folder. I have to do action on this event (i.e. copy file to some other location). When I am putting a large size into the attached watch folder the event rais开发者_开发问答ed immediately even the file copy process still not completed. I don’t want to check this by file.open method.

Is there any way get notify that my file copy process into the watch folder has been completed and then my event get fire.


It is indeed a bummer that FileSystemWatcher (and the underlying ReadDirectoryChangesW API) provide no way to get notified when a new file has been fully created.

The best and safest way around this that I've come across so far (and that doesn't rely on timers) goes like this:

Upon receiving the Created event, start a thread that, in a loop, checks whether the file is still locked (using an appropriate retry interval and maximum retry count). The only way to check if a file is locked is by trying to open it with exclusive access: If it succeeds (not throwing an IOException), then the File is done copying, and your thread can raise an appropriate event (e.g. FileCopyCompleted).


I have had the exact same problem, and solved it this way:

  1. Set FileSystemWatcher to notify when files are created and when they are modified.
  2. When a notification comes in:

    a. If there is no timer set for this filename (see below), set a timer to expire in a suitable interval (I commonly use 1 second).

    b. If there is a timer set for this filename, cancel the timer and set a new one to expire in the same interval.

When a timer expires, you know that the associated file has been created or modified and has been untouched for the time interval. This means that the copy/modify is probably done and you can now process it.


You could listen for the modified event, and start a timer. If the modified event is raised again, reset the timer. When the timer has reached a certain value without the modify event being raised you can try to perform the copy.


I subscribe to the Changed- and Renamed-event and try to rename the file on every Changed-event catching the IOExceptions. If the rename succeeds, the copy has finished and the Rename-event is fired only once.


Three issues with FileSystemWatcher, the first is that it can send out duplicate creation events so you check for that with something like:

this.watcher.Created += (s, e) =>
{
    if (!this.seen.ContainsKey(e.FullPath) 
        || (DateTime.Now - this.seen[e.FullPath]) > this.seenInterval)
    {
        this.seen[e.FullPath] = DateTime.Now;
        ThreadPool.QueueUserWorkItem(
            this.WaitForCreatingProcessToCloseFileThenDoStuff, e.FullPath);
    }
};

where this.seen is a Dictionary<string, DateTime> and this.seenInterval is a TimeSpan.

Next, you have to wait around for the file creator to finish writing it (the issue raised in the question). And, third, you must be careful because sometimes the file creation event gets thrown before the file can be opened without giving you a FileNotFoundException but it can also be removed before you can get a hold of it which also gives a FileNotFoundException.

private void WaitForCreatingProcessToCloseFileThenDoStuff(object threadContext)
{
    // Make sure the just-found file is done being
    // written by repeatedly attempting to open it
    // for exclusive access.
    var path = (string)threadContext;
    DateTime started = DateTime.Now;
    DateTime lastLengthChange = DateTime.Now;
    long lastLength = 0;
    var noGrowthLimit = new TimeSpan(0, 5, 0);
    var notFoundLimit = new TimeSpan(0, 0, 1);

    for (int tries = 0;; ++tries)
    {
        try
        {
            using (var fileStream = new FileStream(
               path, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
            {
                // Do Stuff
            }

            break;
        }
        catch (FileNotFoundException)
        {
            // Sometimes the file appears before it is there.
            if (DateTime.Now - started > notFoundLimit)
            {
                // Should be there by now
                break;
            }
        }
        catch (IOException ex)
        {
            // mask in severity, customer, and code
            var hr = (int)(ex.HResult & 0xA000FFFF);
            if (hr != 0x80000020 && hr != 0x80000021)
            {
                // not a share violation or a lock violation
                throw;
            }
        }

        try
        {
            var fi = new FileInfo(path);
            if (fi.Length > lastLength)
            {
                lastLength = fi.Length;
                lastLengthChange = DateTime.Now;
            }
        }
        catch (Exception ex)
        {
        }

        // still locked
        if (DateTime.Now - lastLengthChange > noGrowthLimit)
        {
            // 5 minutes, still locked, no growth.
            break;
        }

        Thread.Sleep(111);
    }

You can, of course, set your own timeouts. This code leaves enough time for a 5 minute hang. Real code would also have a flag to exit the thread if requested.


This answer is a bit late, but if possible I'd get the source process to copy a small marker file after the large file or files and use the FileWatcher on that.


Try to set filters

myWatcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite;
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜