使用 File.WriteAllText 和 File.ReadAllText 后 WPF 应用程序崩溃

问题描述 投票:0回答:1

我创建了一个 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);
        }
    }
c# wpf
1个回答
0
投票

您不能同时访问一个文件。这引入了各种各样的问题。竞争条件会让你的代码随机失败。您必须锁定文件(读和写锁)以防止副作用。

我通常建议放弃 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);
}
© www.soinside.com 2019 - 2024. All rights reserved.