含FileSystemWatcher的Windows服务启动后异常停止问题排查
问题分析与解决方案
核心原因
FileSystemWatcher的Created事件触发时机是文件目录条目刚被创建时,而非文件完全写入、关闭后。摄像头录制视频时,通常会先创建一个文件(甚至是临时文件),持续写入内容直到录制结束,期间可能还会对文件进行重命名、临时删除等操作。这就导致你的addFile方法触发时,事件里的文件路径可能已经失效(比如临时文件被删除、文件还没完全写入就被改名),哪怕你没直接操作文件,只要代码中涉及到验证该路径的文件存在性、读取文件元数据等操作,就会抛出FileNotFoundException。
解决办法
1. 延迟+重试机制
收到Created事件后,先延迟一段时间等待文件稳定,同时加入重试逻辑,避免单次失败就终止服务:
Private Async Sub addFile(sender As Object, e As FileSystemEventArgs) Dim maxRetries As Integer = 3 Dim delayMs As Integer = 1000 Dim success As Boolean = False For retry = 1 To maxRetries Await Task.Delay(delayMs * retry) ' 每次重试延迟时间递增 Try ' 先验证文件是否存在 If Not File.Exists(e.FullPath) Then Continue For ' 你的数据库写入逻辑 Dim expireDate As Date = Date.Now.AddDays(7) ' 示例:7天后过期 Using conn As New SQLiteConnection("Data Source=VideoFiles.db;Version=3;") conn.Open() Dim cmd As New SQLiteCommand( "INSERT INTO VideoRecords (FilePath, ExpireDate) VALUES (@path, @expire)", conn ) cmd.Parameters.AddWithValue("@path", e.FullPath) cmd.Parameters.AddWithValue("@expire", expireDate) cmd.ExecuteNonQuery() End Using success = True Exit For Catch ex As FileNotFoundException ' 捕获文件不存在异常,继续重试 EventLog.WriteEntry("VideoCleanupService", $"Retry {retry}: File {e.FullPath} not found", EventLogEntryType.Warning) Catch ex As Exception ' 处理其他异常(如数据库错误) EventLog.WriteEntry("VideoCleanupService", $"Error processing {e.FullPath}: {ex.Message}", EventLogEntryType.Error) Exit Sub End Try Next If Not success Then EventLog.WriteEntry("VideoCleanupService", $"Failed to process {e.FullPath} after {maxRetries} retries", EventLogEntryType.Error) End If End Sub
2. 监控Renamed事件
部分摄像头会先写入临时文件(比如temp_xxx.dat),录制完成后重命名为正式视频文件(比如20240520_1230.mp4)。这种场景下,Created事件只会触发临时文件,而你需要的是最终的正式文件路径,因此可以同时监控Renamed事件:
' 初始化时添加Renamed事件监听 watcher.Renamed += AddressOf OnFileRenamed Private Async Sub OnFileRenamed(sender As Object, e As RenamedEventArgs) ' 复用上面的延迟重试逻辑处理最终的文件路径 Await ProcessVideoFile(e.FullPath) End Sub ' 把数据库写入逻辑抽成独立方法,方便复用 Private Async Function ProcessVideoFile(filePath As String) As Task ' 这里放延迟、重试和数据库写入代码,和上面的addFile逻辑一致 End Function
3. 验证文件可访问性
除了检查File.Exists,还可以尝试以只读模式打开文件,确认文件未被摄像头占用:
Private Function IsFileAccessible(filePath As String) As Boolean Try Using fs As New FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.None) Return True End Using Catch ex As IOException ' 文件被占用或无法访问 Return False Catch ex As FileNotFoundException Return False End Try End Function
在处理前调用这个方法,只有返回True时才执行数据库写入。
注意事项
- 根据摄像头的录制时长调整延迟和重试次数,比如短时长视频可以设置1-2秒延迟,长视频适当延长。
- Windows服务中使用
Async/Await时,确保事件处理方法标记为Async,避免阻塞服务主线程。 - 务必在异常捕获中记录详细日志,方便后续排查问题。
内容的提问来源于stack exchange,提问作者Nuggzy
相关产品推荐
相关产品推荐

