2016-03-31 5 views
1

In meiner WPF-Anwendung muss beim Start eine schnelle Routine ausgeführt werden, die nach einer neuen verfügbaren Version sucht. Wenn die Version verfügbar ist, führen wir das Update durch und möchten dann die App sofort neu starten. Da dies ausgeführt wird, bevor das Hauptfenster für den Benutzer angezeigt wird, scheint es einfach so, als ob die Anwendung eine Sekunde länger zum Starten benötigt.Starten Sie die WPF-Anwendung vom Nicht-UI-Thread aus

Wir verwenden Squirrel.Windows für unseren Updater. Ich habe die unten stehende Klasse zur Überprüfung/Anwendung von Updates eingerichtet.

public class UpdateVersion 
{ 
    private readonly UpdateManager _updateManager; 

    public Action<int> Progress; 
    public event Action Restart; 
    public UpdateVersion(string squirrelUrl) 
    { 
     _updateManager = new UpdateManager(squirrelUrl); 
    } 

    public async Task UpdateVersions() 
    { 
     using (_updateManager) 
     { 
      UpdateInfo updateInfo = await _updateManager.CheckForUpdate(progress:Progress); 
      if (updateInfo.CurrentlyInstalledVersion == null) 
      { 
       if (updateInfo.FutureReleaseEntry != null) 
       { 
        await _updateManager.UpdateApp(Progress); 

        // Job crashes here 
        Restart?.Invoke(); 
       } 
      } 
      else if (updateInfo.CurrentlyInstalledVersion.Version < updateInfo.FutureReleaseEntry.Version) 
      { 
       await _updateManager.UpdateApp(Progress); 

       // Job crashes here 
       Restart?.Invoke(); 
      } 
     } 
    } 
} 

Leider Eichhörnchen haben ihren Aktualisierungsprozess async nur vorgenommen, was bedeutet, das CheckForUpdate und UpdateApp Verfahren await verwenden müssen, die gesamten Aktualisierungsverfahren asynchron zu machen. Ich ordne den asnyc-Aufruf einer Task, dann einfach .Wait() für das Update zu beenden.

Das Problem kommt, wenn ich versuche, meine App neu zu starten. Basierend auf dem, was ich gelesen habe, muss ich Dispatcher.Invoke verwenden, um den Neustart aufgrund der Tatsache, dass ich in einem Nicht-UI-Thread bei der Durchführung des Updates bin. Doch trotz des Code unten, ich immer noch die gleiche Fehlermeldung:

Der Aufruf Thread dieses Objekt nicht, weil ein anderer Thread besitzt es zugreifen kann

Jede Idee, wie richtig Dispatcher.Invoke implementieren, um die App neu starten?

 // Instantiate new UpdateVersion object passing in the URL 
     UpdateVersion updateVersion = new UpdateVersion(System.Configuration.ConfigurationManager.AppSettings.Get("SquirrelDirectory")); 

     // Assign Dispatch.Invoke as Restart action delegate 
     updateVersion.Restart +=() => 
     { 
      Dispatcher.Invoke(() => 
      { 
       Process.Start(ResourceAssembly.Location); 
       Current.Shutdown(); 
      }); 
     }; 

     // This is here for debugging purposes so I know the update is occurring 
     updateVersion.Progress += (count) => 
     { 
      Debug.WriteLine($"Progress.. {count}"); 
     }; 

     var task = Task.Run(async() => { await updateVersion.UpdateVersions(); }); 
     task.Wait(); 

EDIT

Unten finden Sie einen Screenshot des Target Attribut des Restart Aktion. Der Debugger wurde in der Zeile Restar?.Invoke von oben angehalten.

enter image description here

+0

Wo ist der Code ausgeführt wird, ist es in Ihrem Programm. cs oder eine 'Application' Veranstaltung? – CodingGorilla

+0

Keine Program.cs-Datei es ist die OnStartup-Methode der Anwendung. Dies ist der erste Befehl, den das Programm ausführt. – NealR

+0

Sie könnten in Betracht ziehen, '' Program.cs' 'mit einer 'Main'-Methode (zB einer Konsolen-App) zu ergänzen, diese als Startobjekt (unter Eigenschaften -> Anwendung) festzulegen und dann von dort aus zu aktualisieren. Auf diese Weise können Sie einfach wählen, das Hauptfenster nicht zu starten, wenn ein Update durchgeführt werden muss. – CodingGorilla

Antwort

0

So ist die eigentliche Ausnahme ist irgendwo in dem Restart Handler versucht, die MainWindow Eigenschaft von einem anderen Thread auf dem Stack-Trace auf Basis bekommen zuzugreifen. Dies ist eine vollständige Schätzung, aber ich würde das Original in der OnStartup Methode speichern und den gespeicherten Dispatcher im Restart Event-Handler verwenden.

1

Statt asynchrone Programmierung auf die alten ereignisbasierte Muster zu konvertieren, zu versuchen, es ist nur richtig verwenden. Sie benötigen keine Ereignisse, um zu erkennen, wann eine asynchrone Operation beendet wurde, und Sie brauchen auch nicht Invoke, um zum UI-Thread zurückzukehren. await kümmert sich um beides.

Sie Code so einfach wie diese schreiben könnte:

static readonly SemanticVersion ZeroVersion = new SemanticVersion(0, 0, 0, 0); 


private async void Application_Startup(object sender, StartupEventArgs e) 
{ 
    await CheckForUpdatesAsync(); 
} 

private async Task CheckForUpdatesAsync() 
{ 
    string squirrelUrl = "..."; 


    var updateProgress = new Progress<int>(); 
    IProgress<int> progress = updateProgress; 

    //Create a splash screen that binds to progress and show it 
    var splash = new UpdateSplash(updateProgress); 
    splash.Show(); 

    using (var updateManager = new UpdateManager(squirrelUrl)) 
    { 

     //IProgress<int>.Report matches Action<i> 
     var info = await updateManager.CheckForUpdate(progress: progress.Report); 

     //Get the current and future versions. 
     //If missing, replace them with version Zero 
     var currentVersion = info.CurrentlyInstalledVersion?.Version ?? ZeroVersion; 
     var futureVersion = info.FutureReleaseEntry?.Version ?? ZeroVersion; 

     //Is there a newer version? 
     if (currentVersion < futureVersion) 
     { 
      await updateManager.UpdateApp(progress.Report); 
      Restart(); 
     } 
    } 
    splash.Hide(); 
} 

private void Restart() 
{ 
    Process.Start(ResourceAssembly.Location); 
    Current.Shutdown(); 
} 

Dies gerade genug Code ist in eine separate Klasse zu extrahieren:

private async void Application_Startup(object sender, StartupEventArgs e) 
{ 
     var updater = new Updater(); 
     await updater.CheckForUpdatesAsync(...); 
} 

// ... 

class Updater 
{ 
    static readonly SemanticVersion ZeroVersion = new SemanticVersion(0, 0, 0, 0); 


    public async Task CheckForUpdatesAsync(string squirrelUrl) 
    { 
     var updateProgress = new Progress<int>(); 
     IProgress<int> progress = updateProgress; 

     //Create a splash screen that binds to progress and show it 
     var splash = new UpdateSplash(updateProgress); 
     splash.Show(); 

     using (var updateManager = new UpdateManager(squirrelUrl)) 
     { 

      var updateInfo = await updateManager.CheckForUpdate(progress: progress.Report); 

      //Get the current and future versions. If missing, replace them with version Zero 
      var currentVersion = updateInfo.CurrentlyInstalledVersion?.Version ?? ZeroVersion; 
      var futureVersion = updateInfo.FutureReleaseEntry?.Version ?? ZeroVersion; 

      //Is there a newer version? 
      if (currentVersion < futureVersion) 
      { 
       await updateManager.UpdateApp(progress.Report); 
       Restart(); 
      } 
     } 
     splash.Hide(); 
    } 

    private void Restart() 
    { 
     Process.Start(Application.ResourceAssembly.Location); 
     Application.Current.Shutdown(); 
    } 
}