英文:
Best way to handle a case where FileSystemWatcher in C# ISN'T thread safe?
问题
以下是您要翻译的内容:
**注意:**关于线程安全和FileSystemWatcher的问题在Stack Overflow上有很多,但它们大多都以相同的结果结束:它是线程安全的,只有在将其整合到某种GUI中时才需要处理线程。然而,我有一个情况,我无法确定我所拥有的东西是否是线程安全的,我正在寻求关于如何处理的建议。
背景:
我有一个FileSystemWatcher对象,用于监视一个日志文件(简单的.txt)的更改。如果文件发生更改,它会读取文件(但只读取最新添加的内容)并将数据存储在另一个变量/对象中。
tailFileCmd(ref system, 5);
}
private void tailFileCmd(ref Logs log, int tailAmount = 75) {
if (log.safeToRead) {
log.lines.Clear();
var result = CMDder.ExecuteCmd("tail -" + tailAmount.ToString() + " " + log.location);
if (!result.HasError && result.ExitCode == 0) {
var split = result.Result.Split('\n');
if (split.Length > 0) {
foreach (string line in split) {
log.lines.Add(line);
}
}
}
}
}```
稍后我必须处理多个文件,因此我创建了一个类来存储每个文件所需的所有数据,包括它自己的监视程序。不过,我不确定是否在同一个对象中拥有监视程序和文件内容会有问题。
```public class Logs {
public logType type = logType.NOT_SET;
public string location = string.Empty;
public StreamReader? file;
public FileSystemWatcher watcher = new FileSystemWatcher();
public List<string> lines = new List<string>();
public bool safeToRead = false;
}```
safeToRead属性在对象初始化时设置为true,如果文件路径正确,而且StreamReader和FileSystemWatcher都正确初始化。CMDer是一个自定义类(来自另一个项目),它只执行系统命令。我知道它不是必需的,但我只是为了简单起见在这里使用它。此外,存在一个StreamReader是为了以后使用(它也需要是线程安全的,但一次只解决一个问题)。
上述代码的作用是保持lines列表与最新内容保持同步,只有在文件更改时才更新。
**问题:**
我在另一个文件中有另一个函数,将定期调用以从logs对象获取数据,特别是lines,以将其传递到其他地方。根据我目前的设置,我认为这不是线程安全的(但在我的情况下很难进行测试,因为我必须精确地将时间安排在一起)。如果我的理论是正确的,如何使上述代码成为线程安全,以便如果在我尝试读取它的同时lines列表正在更新,其中一个函数(不管哪个函数)等待另一个函数先完成后再执行?我对C#中的线程没有太多经验,只有在C++中有一些经验。
**编辑:**关于我的代码有一些混淆。澄清一下,代码不会修改文件。文件会被完全不同的程序修改。当我说“写入”时,我指的是将文件的内容存储到一个变量中,该变量是一个字符串列表,其中每个字符串是文件中的一行。
<details>
<summary>英文:</summary>
**Note:** There are a lot of questions on stack overflow regarding thread safety and FileSystemWatcher, but they all end in mostly the same result: it's thread safe and you only need to mess with threads if you're incorporating it into a gui of some sort. However, I have a case where I can't figure out if what I have is thread safe or not and I'm looking for advise on what to do.
**THE CONTEXT:**
I have a FileSystemWatcher object that watches a log file (simple .txt) for changes. If the file changes, it reads the file (but only the newest content added) and stores the data in another variable/object.
private void onChangeSystem(object sender, FileSystemEventArgs e) {
tailFileCmd(ref system, 5);
}
private void tailFileCmd(ref Logs log, int tailAmount = 75) {
if (log.safeToRead) {
log.lines.Clear();
var result = CMDder.ExecuteCmd("tail -" + tailAmount.ToString() + " " + log.location);
if (!result.HasError && result.ExitCode == 0) {
var split = result.Result.Split('\n');
if (split.Length > 0) {
foreach (string line in split) {
log.lines.Add(line);
}
}
}
}
}
I have to deal with multiple files later, so I created a class to store all the data needed for each file, including its own watcher. I do wonder if having the watcher and the file content in the same object will be problematic though.
public class Logs {
public logType type = logType.NOT_SET;
public string location = string.Empty;
public StreamReader? file;
public FileSystemWatcher watcher = new FileSystemWatcher();
public List<string> lines = new List<string>();
public bool safeToRead = false;
}
The boolean at the is set to true during initialization of the object if the file paths are correct and both StreamReader and FileSystemWatcher are initialized correctly. CMDer is a custom class (from another project) imported in that just executes system commands. I know it's not required but I'm just using it for simplicity here. Also, the existence of a StreamReader is for use later (which will also need to be thread safe, but one problem at a time).
The above code works in that it keeps the lines list up to date with the most recent content, and only updates if the file changes.
**THE PROBLEM:**
I have another function in another file that will be making regular calls to get the data from the logs object, specifically the lines, in order to pass it off somewhere else. With the current setup I have, I do not believe this is thread-safe (but it's difficult to test in my case as I'd have to get the timings perfectly lined up). If my theory is correct, how do I make the above code thread-safe so that if the lines list is being updated at the exact same time that I try to read it, one of the functions (doesn't matter which one) waits for the other to finish first before going? I don't have a lot of experience with threads in C#, only in C++.
**EDIT:** There seems to be some confusion regarding what is going on with my code. To clarify, the code is NOT modifying the file. The file gets modified by an entirely different program. When I say I'm writing, I'm referring to storing the contents OF THE FILE to a variable that is a list of strings, where each string is a line in the file.
</details>
# 答案1
**得分**: 1
如果文件的写入者和读取者位于同一进程中,您可能应该使用其他方法来更新数据。您的日志框架可能有一些自定义的日志写入器,允许您拦截日志消息而无需重新从文件中读取它们,或者您可以为您的日志记录器接口创建一个执行相同操作的装饰器。在任何情况下,围绕普通列表加锁应足以确保不能同时进行读取和写入。
如果写入者位于另一个进程中,您可以使用命名的[互斥体](https://learn.microsoft.com/en-us/dotnet/api/system.threading.mutex?view=net-7.0)来创建跨进程锁,但这将需要对两个进程进行更改。您还可以考虑使用一些进程间通信方法,而不是文件,这可能会更可靠。我听说`FileSystemWatcher`不太可靠,这可能是因为使用不正确,但如果可能的话,我可能更喜欢其他方法。
如果无法修改写入者,我不确定应该如何操作。锁定文件可能会导致写入进程失败。也许创建文件的影子副本?
<details>
<summary>英文:</summary>
If the writer and reader of the file is within the same process, you should probably use some other method to update the data. Your logging framework might have some custom log writer that allow you to intercept log messages without having to re-read them from file, or you could just create a decorator for your logger interface that does the same thing. In either case, a lock around a regular list should be sufficient to ensure reading and writing cannot be done concurrently.
If the writer is in another process you can use a named [mutex][1] to create a cross process lock, but this will require changes to both processes. You might also consider using some inter process communication method instead of a file, that will likely be more reliable. I have heard rumors that `FileSystemWatcher` is not very reliable, that might be due to incorrect usage, but I would probably prefer other methods if possible.
If modification of the writer is not possible I'm not sure how it should be done. Locking the file might cause the writing process to fail. Maybe create a shadow copy of the file?
[1]: https://learn.microsoft.com/en-us/dotnet/api/system.threading.mutex?view=net-7.0
</details>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论