【问题标题】:Starting simple C# FileSystemWatcher Windows service quickly快速启动简单的 C# FileSystemWatcher Windows 服务
【发布时间】:2014-05-21 19:34:53
【问题描述】:

我有一个使用 C# 编写的 Windows 服务。在幕后它是一个 FileSystemWatcher。 FSW 查找新文件并相应地处理它们。当我的服务启动时,它还需要处理现有文件。当我通过控制台应用程序执行此操作时,一切都按预期工作。

但是,当我尝试将这一切包装在 Win 服务中时,我的第一个问题是 Win 服务无法启动。超时是因为,即使最初要处理的文件很多,但处理时间太长。

这是我的“观看”类的部分代码:

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

我的解决方法是启动 FSW“监视”并在单独的异步线程上处理初始文件,如下所示(在我的 Windows 服务代码中):

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

如果我没有在 Task.Factory.StartNew() 中包装那个“StartWatching”方法,OnStart() 就会超时,这是可以理解的。但现在看来我的 StartWatching() 方法从未被调用过。我在日志中看到“LoggingService 启动”,但没有看到“WatcherService 启动”。 (编辑:仅供参考,我也尝试过 Task.Run(),但无济于事。)

那是怎么回事?我确定我要么不明白 StartNew() 在做什么和/或有更好的方法来完成我想要完成的工作。

想法?

谢谢!

【问题讨论】:

  • 尝试使用不同的条件语句,看看输出是什么(如果你还没有尝试过)。

标签: c# filesystemwatcher


【解决方案1】:

您可以完全避免线程化。只需在OnStart() 方法中进行基本设置。该设置的一部分是设置一个计时器以在一两秒内关闭。该计时器可以在当前线程上运行,但会在服务空闲之后发生。

这样就解决了问题,写线程安全的代码也更容易了。

【讨论】:

    【解决方案2】:

    我不知道 Task.Factory.StartNew... 但我有一些类似的服务,我使用 ThreadPool 来处理这类事情。

    另外,我不知道如何在单独的线程中运行 StartWatching - 根据您的解释,我会将 ProcessExistingFiles 放在单独的线程中。示例:

    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
    }
    

    【讨论】:

      【解决方案3】:

      服务必须设计为尽快从 OnStart 中返回;可能在前 30 秒内。你只需要在里面做初始化动作。

      您可以使用两种方法来启动您的服务:
      1.为长时间运行的操作创建一个线程
      2. 启动至少一个异步操作(定时器、FileSystemWatching)

      1. 如果你有一个长时间运行的操作,你肯定必须启动一个新线程:

        public void OnStart(string[] args) 
        {
            var worker = new Thread(DoWork);
            worker.Name = "MyWorker";
            worker.IsBackground = false;
            worker.Start();
        }
        
        void DoWork()
        {
            // long running task
        }
        
      2. 在您的情况下,您长期运行的操作正在监视文件更改。当您使用 FileWatcher 时,您可以在 OnStart 方法中开始文件监视,并使用线程池启动您的短期运行任务:

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

      【讨论】:

      • 从性能的角度来看,使用 ThreadPool 比创建新线程更好。通常,如果您不知道两者之间的区别,那么您(几乎)总是最好使用 ThreadPool。如果你对差异感兴趣,StackOverflow 上有很多很好的讨论,比如这个:stackoverflow.com/questions/2049948/…
      • @kape123 无论如何你必须创建一个线程。由于这是一个长时间运行的任务,而且你只有一个任务,只需要创建一个线程,因此最好创建一个新线程而不是使用线程池(stackoverflow.com/a/3344692/3523746
      猜你喜欢
      • 1970-01-01
      • 2012-03-29
      • 1970-01-01
      • 2015-06-11
      • 1970-01-01
      • 1970-01-01
      • 2011-04-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多