2016-03-31 2 views
1

В моем приложении WPF мне нужно запустить быстрый запуск при запуске, который проверяет наличие новой доступной версии. Если версия доступна, мы делаем обновление, а затем хотим немедленно перезапустить приложение. Поскольку это выполняется до появления главного окна пользователю, оно просто выглядит так, как если бы приложение заняло секундную паузу для запуска.Перезапустить приложение WPF из потока, отличного от UI

Мы используем Squirrel.Windows для нашего обновления. Я сделал класс ниже, чтобы обрабатывать проверку/применение обновлений.

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(); 
      } 
     } 
    } 
} 

К сожалению Белка сделал свой процесс обновления async только, что означает, что CheckForUpdate и UpdateApp метод должен использовать await, что делает весь метод обновления асинхронно. Я назначаю вызов asnyc для Task, а затем просто .Wait() для завершения обновления.

Проблема возникает, когда я пытаюсь перезагрузить приложение. Основываясь на том, что я прочитал, мне нужно использовать Dispatcher.Invoke, чтобы вызвать перезапуск из-за того, что при выполнении обновления я нахожусь в потоке, отличном от UI. Тем не менее, несмотря на код ниже, я все еще получаю такое же сообщение об ошибке:

Вызывающий поток не может получить доступ к этому объекту, потому что другой поток владеет

Любая идея, как правильно реализовать Dispatcher.Invoke для того, чтобы перезапустить приложение?

 // 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

Ниже приведен снимок экрана атрибута Restart действия Target. Отладчик был приостановлен на линии Restar?.Invoke сверху.

enter image description here

+0

Где работает код, это в вашей программе. cs или событие 'Application'? – CodingGorilla

+0

Нет файла Program.cs, это метод «OnStartup» приложения. Это первая команда, выполняемая программой. – NealR

+0

Вы можете подумать о добавлении 'Program.cs' с помощью метода' Main' (например, консольного приложения), установите это как ваш объект запуска (в разделе «Свойства -> Приложение»), а затем выполните обновление. Таким образом, вы можете просто не запускать основное окно, когда есть обновление, которое нужно выполнить. – CodingGorilla

ответ

0

Таким образом, реальное исключение находится где-то в Restart обработчик пытается получить доступ к MainWindow получить свойство из другого потока на основе трассировки стека. Это полная догадка, но я бы сохранил оригинал Dispatcher в методе OnStartup и использовал хранимый диспетчер в обработчике событий Restart.

1

Вместо того, чтобы пытаться преобразовать асинхронное программирование в старый шаблон на основе событий, просто использовать его правильно. Вам не нужны события, чтобы определить, когда завершена асинхронная операция, и вам не нужно Invoke, чтобы вернуться к потоку пользовательского интерфейса. await заботится обоим.

Вы могли бы написать код так просто, как это:

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(); 
} 

Это просто достаточно кода, чтобы извлечь в отдельный класс:

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(); 
    } 
} 
Смежные вопросы