简体   繁体   中英

Starting simple C# FileSystemWatcher Windows service quickly

I have a Windows service that I am writing in C#. Behind the scenes it a FileSystemWatcher. The FSW looks for new files and processes them accordingly. When my service starts, it also needs to process existing files. When I do this via a console app, everything works as expected.

However, when I try to wrap this all in a Win service, my first issue was that the Win service would not start. It timed out because, in the even there are a lot of files to be processed initially, it took too long to process.

Here is a portion of the code for my "watching" class:

public WatcherService()
{
    _log.Debug("WatcherService instantiated.");
    _watcher = new FileSystemWatcher { Path = AppConfig.MonitorFolder, IncludeSubdirectories = true };

    // we want the watching to start BEFORE we process existing files
    // because if we do it the other way, a file might get missed
    _watcher.Created += File_OnChanged;
}

public void StartWatching()
{
    _log.Debug("WatcherService started.");
    // this kicks off the watching
    _watcher.EnableRaisingEvents = true; 

    // process existing files
    ProcessExistingFiles(AppConfig.MonitorFolder);
}

My workaround was to kick off the FSW "watching" and the processing of the initial files on a separate asynchronous thread, like this (in my Windows service code):

protected override void OnStart(string[] args)
{
    _log.Debug("LoggingService starting.");

    // kick off the watcher on another thread so that the OnStart() returns faster; 
    // otherwise it will hang if there are a lot of files that need to be processed immediately
    Task.Factory.StartNew(() => _watcher.StartWatching()).ContinueWith(t =>
        {
            if (t.Status == TaskStatus.Faulted)
            {
                _log.Error("Logging service failed to start.", t.Exception.InnerException ?? t.Exception);
            }
        });
}

If I did not wrap that "StartWatching" method in the Task.Factory.StartNew(), the OnStart() timed out, understandably so. But now it seems my StartWatching() method is never called. I see "LoggingService starting" in my logs, but not "WatcherService started". (Edit: FYI I have tried Task.Run() as well, to no avail.)

What up with that? I am sure I either don't understand what StartNew() is doing and/or there is a better to do what I am trying to accomplish.

Thoughts?

Thanks!

You can avoid threading entirely. Just do basic setup in the OnStart() method. Part of that setup is setting up a timer to go off in a second or two. That timer can run on the current thread, but will happen after the service is idle .

This will solve the issue, and it's easier that writing thread-safe code.

The service must be designed to return from OnStart as quickly as possible; possibly within the first 30 seconds. You have to do only initialization action inside.

You can use two approaches for starting your service:
1. Create a thread for a long running operation
2. Start at least one async operation (timer, FileSystemWatching)

  1. If you have a long running operation you definitely have to start a new thread:

     public void OnStart(string[] args) { var worker = new Thread(DoWork); worker.Name = "MyWorker"; worker.IsBackground = false; worker.Start(); } void DoWork() { // long running task } 
  2. In your case, your long running operation is watching for file changes. As you use FileWatcher, you can start file watching inside OnStart method and start your short running task using thread pool:

     protected override void OnStart(string[] args) { _log.Debug("LoggingService starting."); _log.Debug("Initializing FileSystemWatcher ."); _watcher = new FileSystemWatcher { Path = AppConfig.MonitorFolder, IncludeSubdirectories = true }; _watcher.Created += File_OnChanged; _watcher.EnableRaisingEvents = true; _log.Debug("FileSystemWatcher has been started."); ThreadPool.QueueUserWorkItem(ProcessExistingFiles, AppConfig.MonitorFolder); _log.Debug("Processing of existing files has been queued."); } private void ProcessExistingFiles(object folderPathObj) { var folderPath = folderPathObj as string; if (folderPath != null) { } } 

I don't know about Task.Factory.StartNew... but I have somewhat similar service and I use ThreadPool for this kind of stuff.

Also, I don't know about running StartWatching in separate thread - from what you've explained I would put ProcessExistingFiles in separate thread. Example:

public WatcherService()
{
    _log.Debug("WatcherService instantiated.");
    _watcher = new FileSystemWatcher { Path = AppConfig.MonitorFolder, IncludeSubdirectories = true };

    // we want the watching to start BEFORE we process existing files
    // because if we do it the other way, a file might get missed
    _watcher.Created += File_OnChanged;
}

public void StartWatching()
{
    _log.Debug("WatcherService started.");
    // this kicks off the watching
    _watcher.EnableRaisingEvents = true; 

    // process existing files

    // keep in mind that method should be
    // void ProcessExistingFiles(object param)
    // in order to satisfy Waitcallback delegate
    ThreadPool.QueueUserWorkItem(ProcessExistingFiles, AppConfig.MonitorFolder);

    // since you are now starting async - be sure not to process files multiple times
    // i.e. from File_OnChanged and ProcessExistingFiles
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM