我创建了一个 WPF 应用程序。该应用程序的一个实例始终在后台运行(没有 UI),它充当一项服务来检测来自 API 的新消息。当它检测到新消息时,会将文本写入 txt 文件。运行时的另一个实例有 UI,它监听文件中的更改并根据 txt 文件内容执行某些操作。我遇到的问题是,有时应用程序在启动时会崩溃(带有 UI 的实例)。我怀疑是因为文件的原因,所以我尝试了:
用于将消息写入txt文件的组件:
private Task WriteMessageToFile()
{
try
{
string folderPath = @"C:\ProgramData\Service";
string filePath = Path.Combine(folderPath, "Message.txt");
if (!Directory.Exists(folderPath))
{
Directory.CreateDirectory(folderPath);
}
File.WriteAllText(filePath, "some_message_string");
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
return Task.CompletedTask;
}
视图模型:
public class MainViewVM : ViewModelBase
{
FileWatcherService _fileWatcherService = new();
private string file = "Message.txt";
private string filePath = @"C:\ProgramData\Service";
// CONSTRUCTOR
public MainViewVM()
{
_fileWatcherService.FileChanged += OnFileChanged;
}
private void OnFileChanged(object sender, FileSystemEventArgs e)
{
HandleNavigation();
}
private void HandleNavigation()
{
try
{
string fullPath = Path.Combine(filePath, file);
if (File.Exists(fullPath))
{
string fileContent = File.ReadAllText(fullPath);
// if there is text in the file, do something, then clear the file
if (!string.IsNullOrEmpty(fileContent))
{
// DO SOMETHING
File.WriteAllText(fullPath, "");
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
文件观察服务:
public class FileWatcherService
{
FileSystemWatcher watcher;
public FileWatcherService()
{
CreateFileWatcher();
}
public event EventHandler<FileSystemEventArgs> FileChanged;
public void CreateFileWatcher()
{
watcher = new FileSystemWatcher(@"C:\ProgramData\DPMService", "Message.txt")
{
NotifyFilter = NotifyFilters.Attributes
| NotifyFilters.CreationTime
| NotifyFilters.DirectoryName
| NotifyFilters.FileName
| NotifyFilters.LastAccess
| NotifyFilters.LastWrite
| NotifyFilters.Security
| NotifyFilters.Size
};
watcher.Changed += MakeANote;
watcher.Created += MakeANote;
watcher.Deleted += MakeANote;
watcher.Renamed += MakeANote;
watcher.Filter = "*.*";
watcher.IncludeSubdirectories = true;
watcher.EnableRaisingEvents = true;
}
internal void Dispose()
{
if (watcher != null)
{
watcher.Changed -= MakeANote;
watcher.EnableRaisingEvents = false;
watcher.Dispose();
}
}
private void MakeANote(object sender, FileSystemEventArgs e)
{
FileChanged?.Invoke(this, e);
}
}
您不能同时访问一个文件。这引入了各种各样的问题。竞争条件会让你的代码随机失败。您必须锁定文件(读和写锁)以防止副作用。
我通常建议放弃 I/O 文件并查看内存映射文件。
但是,如果您想坚持使用通用 I/O 文件,那么您应该在共享资源的进程之间应用一种跨进程同步。为此,您应该放开
FileSystemWatcher
并使用 EventWaitHandle
允许服务(服务器)向 UI 应用程序(客户端)发出文件已更改的信号。并使用 Mutex
实现文件的跨进程锁定(例如,避免竞争条件)。
服务器
将新消息写入文件并向客户端发出有关更改的信号。
// Important: the resource names must be exactly the same
// in client(s) and server(s) as they serve as the ID of the IPC
// synchronization primitive that is managed by the OS.
private const string WaitHandleName = @"Local\MessageReadySignal";
private const string MutexName = @"Local\FileAccessLock";
private EventWaitHandle messageReadySignal;
private Mutex fileLock;
public Ctor()
{
Initialize();
}
private void Initialize()
{
SecurityIdentifier currentUser = WindowsIdentity.GetCurrent().User;
if (!EventWaitHandleAcl.TryOpenExisting(WaitHandleName, EventWaitHandleRights.Synchronize, out EventWaitHandle waitHandle))
{
EventWaitHandleSecurity handleSecurity = new EventWaitHandleSecurity();
EventWaitHandleAccessRule handleSecurityRule = new EventWaitHandleAccessRule(
currentUser,
EventWaitHandleRights.Synchronize | EventWaitHandleRights.Modify,
AccessControlType.Allow);
handleSecurity.AddAccessRule(handleSecurityRule);
waitHandle = EventWaitHandleAcl.Create(false, EventResetMode.AutoReset, WaitHandleName, out _, handleSecurity);
}
this.messageReadySignal = waitHandle;
if (!MutexAcl.TryOpenExisting(MutexName, MutexRights.Modify | MutexRights.Synchronize, out Mutex mutex))
{
var mutexSecurity = new MutexSecurity();
var mutexSecurityRule = new MutexAccessRule(
currentUser,
MutexRights.Modify | MutexRights.Synchronize,
AccessControlType.Allow);
mutexSecurity.AddAccessRule(mutexSecurityRule);
mutex = MutexAcl.Create(false, MutexName, out _, mutexSecurity);
}
this.fileLock = mutex;
}
private void SendMessage()
{
try
{
_ = this.fileLock.WaitOne();
WriteMessageToFile();
}
finally
{
this.fileLock.ReleaseMutex();
this.messageReadySignal.Set();
}
}
客户
从服务器发出信号后从文件中读取。
// Important: the resource names must be exactly the same
// in client(s) and server(s) as they serve as the ID of the IPC
// synchronization primitive that is managed by the OS.
private const string WaitHandleName = @"Local\MessageReadySignal";
private const string MutexName = @"Local\FileAccessLock";
private EventWaitHandle messageReadySignal;
private Mutex fileLock;
private readonly CancellationTokenSource messageObserverCancellationTokenSource;
public Ctor()
{
Initialize();
this.messageObserverCancellationTokenSource = new CancellationTokenSource();
ObserveMessageService(this.messageObserverCancellationTokenSource.Token);
}
private void Initialize()
{
SecurityIdentifier currentUser = WindowsIdentity.GetCurrent().User;
if (!EventWaitHandleAcl.TryOpenExisting(WaitHandleName, EventWaitHandleRights.Synchronize, out EventWaitHandle waitHandle))
{
EventWaitHandleSecurity handleSecurity = new EventWaitHandleSecurity();
EventWaitHandleAccessRule handleSecurityRule = new EventWaitHandleAccessRule(
currentUser,
EventWaitHandleRights.Synchronize | EventWaitHandleRights.Modify,
AccessControlType.Allow);
handleSecurity.AddAccessRule(handleSecurityRule);
waitHandle = EventWaitHandleAcl.Create(false, EventResetMode.AutoReset, WaitHandleName, out _, handleSecurity);
}
this.messageReadySignal = waitHandle;
if (!MutexAcl.TryOpenExisting(MutexName, MutexRights.Modify | MutexRights.Synchronize, out Mutex mutex))
{
var mutexSecurity = new MutexSecurity();
var mutexSecurityRule = new MutexAccessRule(
currentUser,
MutexRights.Modify | MutexRights.Synchronize,
AccessControlType.Allow);
mutexSecurity.AddAccessRule(mutexSecurityRule);
mutex = MutexAcl.Create(false, MutexName, out _, mutexSecurity);
}
this.fileLock = mutex;
}
private void ObserveMessageService(CancellationToken cancellationToken)
{
_ = Task.Run(() =>
{
while (!cancellationToken.IsCancellationRequested)
{
_ = this.messageReadySignal.WaitOne();
try
{
this.fileLock.WaitOne();
GetNewMessageFromFile();
}
finally
{
this.fileLock.ReleaseMutex();
}
}
}, cancellationToken);
}