2016-05-12 16 views
6

Ho un problema davvero pazzo quando si lavora con un'applicazione WPF che utilizza il modello di istanza Singleton per assicurare che sia in esecuzione una singola istanza. I meccanismi di rilevamento di istanze singole e di inoltro della riga di comando funzionano bene, tuttavia, come parte del codice di avvio che termina su istanze secondarie che scrivono un file su disco che viene prelevato dall'applicazione principale tramite un FileWatcher. L'istanza secondaria spesso causa un arresto anomalo dello con un errore di livello del kernel.WPF App.OnStartup() si blocca con la scrittura di file e FileWatcher

Il codice di avvio che controlla per le istanze secondarie e casualmente si blocca fa questo:

protected override void OnStartup(StartupEventArgs e) 
    { 
      bool isOnlyInstance = false; 
      Mutex = new Mutex(true, @"MarkdownMonster", out isOnlyInstance); 
      if (!isOnlyInstance) 
      { 
       filesToOpen = " "; 
       var args = Environment.GetCommandLineArgs(); 
       if (args != null && args.Length > 1) 
       { 
        StringBuilder sb = new StringBuilder(); 
        for (int i = 1; i < args.Length; i++) 
        { 
         sb.AppendLine(args[i]); 
        } 
        filesToOpen = sb.ToString(); 
       } 

       File.WriteAllText(mmApp.Configuration.FileWatcherOpenFilePath, filesToOpen); 

       Mutex.Dispose(); 

       // This blows up when writing files and file watcher watching 
       // No idea why - Environment.Exit() works with no issue 
       ShutdownMode = ShutdownMode.OnMainWindowClose; 
       App.Current.Shutdown(); 

       return; 
      } 

     // ...   
    } 

Il codice che controlla per il file che è stato scritto è caricato nel costruttore del form principale:

  openFileWatcher = new FileSystemWatcher(
       Path.GetDirectoryName(mmApp.Configuration.FileWatcherOpenFilePath), 
       Path.GetFileName(mmApp.Configuration.FileWatcherOpenFilePath)) 
      { 
       NotifyFilter = NotifyFilters.LastWrite, 
       EnableRaisingEvents = true 
      }; 
      openFileWatcher.Changed += openFileWatcher_Changed; 
      openFileWatcher.Created += openFileWatcher_Changed; 

E il gestore controlla quindi il file in questo modo:

private void openFileWatcher_Changed(object sender, FileSystemEventArgs e) 
    { 
     string filesToOpen = null; 

     // due to write timing we may have to try a few times 
     for (int i = 0; i < 100; i++) 
     { 
      try 
      { 
       if (File.Exists(mmApp.Configuration.FileWatcherOpenFilePath)) 
       { 
        filesToOpen = File.ReadAllText(mmApp.Configuration.FileWatcherOpenFilePath); 
        File.Delete(mmApp.Configuration.FileWatcherOpenFilePath); 
        filesToOpen = filesToOpen.TrimEnd(); 
       }     
       break; 
      } 
      catch 
      { 
       Thread.Sleep(10); 
      } 
     } 

     Dispatcher.Invoke(() => 
     { 

      if (!string.IsNullOrEmpty(filesToOpen)) 
      { 
       foreach (var file in StringUtils.GetLines(filesToOpen)) 
       { 
        MessageBox.Show(file); 
        this.OpenTab(file.Trim()); 
       } 
      } 

      if (WindowState == WindowState.Minimized) 
       WindowState = WindowState.Normal; 

      this.Activate();  
     }); 
    } 

Th La logica per tutto ciò va bene. L'applicazione rileva correttamente l'istanza secondaria che scrive sempre il file e la prima istanza preleva il file e attiva/carica i file specificati nella riga di comando.

Tuttavia, l'istanza secondaria crash difficile con un errore untrappable (AppDomain.UnhandledException evento è agganciato, ma non si accende), che apre una finestra di errore di Windows sul desktop.

I carichi secondari si bloccano circa l'80% del tempo in cui vengono avviati: è incoerente, ma molto più frequentemente.

Se rimuovo il codice File.WriteAllText(), non si verifica alcun arresto anomalo. Se rimuovo il codice FileWatcher, non si verifica alcun arresto anomalo. Se entrambi sono attivi: Boom. IOW, sia la scrittura di file che FileWatcher devono accadere per arrestarsi in modo anomalo: se uno non è attivo, non si verifica un arresto anomalo. Ho provato a racchiudere la chiamata File.WriteAllText() con try/catch, ma non viene attivata. L'errore si verifica dopo che il codice è uscito dalla funzione utente e sembra che non abbia alcun controllo sull'errore.

altre stranezze:

  • Il fallimento non si verifica in Debug
  • Non è possibile allegare un debugger dal crash di Windows
  • MessageBox.Show() in OnStartup() appena lampeggia il MB (non modale)

ho anche provato a sostituire il codice App.Current.Shutdown() con Environment.Exit() che è meglio - gli arresti sono molto meno frequenti, ma st mal accadono circa il 10% del tempo.

Cosa potrebbe causare questo arresto anomalo dell'applicazione, quando tutta l'istanza secondaria sta eseguendo la scrittura di un file su disco?

UPDATE
Così si scopre che il problema crash non è correlato al l'operazione di scrittura dei file/FileWatcher a tutti. Ho creato una versione NamedPipe dello stesso codice e ho ancora riscontrato errori.

Si scopre che il vero colpevole è stato lo SplashScreen che WPF utilizza per avviare un'immagine sullo schermo all'avvio. Sebbene non sia una "finestra" completa in termini WPF, questa finestra viene avviata su un nuovo thread e si chiude prima che l'inizializzazione completa uccida l'applicazione prima che il thread di SplashScreen possa essere completato, cosa che causa l'arresto anomalo del kernel. La soluzione è a) rimuovere lo splash screen, b) gestire manualmente la schermata iniziale e non mostrarlo al ritiro anticipato o c) chiudere la schermata iniziale in modo esplicito prima di uscire:

SplashScreen.Close(TimeSpan.MinValue); 
Environment.Exit(0); 

ho scritto su un post che in parte copre la questione più in dettaglio:

http://weblog.west-wind.com/posts/2016/May/13/Creating-Single-Instance-WPF-Applications-that-open-multiple-Files

+0

Aspetta, il "singleton" di cui stai parlando si riferisce semplicemente al fatto che l'applicazione non può essere aperta più di una volta in una sola volta? – Jai

+0

* implementazione del modello di istanza Singleton. Il rilevamento e la meccanica delle singole istanze funzionano bene, tuttavia, come parte del codice di avvio che si chiude su ** istanze secondarie **, *. Mi dispiace se ho capito male, ma vuoi un singleton? – StepUp

risposta

0

provare a utilizzare FileStream, invece, allora si può assicurare la chiusura del manico file con FileStream.Flush

using (FileStream fs = File.Create(mmApp.Configuration.FileWatcherOpenFilePath)) 
{ 
    byte[] info = new UTF8Encoding(true).GetBytes(filesToOpen); 
    fs.Write(info, 0, info.Length); 
    fs.Flush(); 
} 
+0

Questa è una delle prime cose che ho provato, ma ciò non fa alcuna differenza: vedo lo stesso comportamento con scritture/chiusure di file di livello inferiore. –