[英]Restart WPF Application from non-UI thread
In my WPF app I need to run a quick routine on startup that checks for a new available version.在我的 WPF 应用程序中,我需要在启动时运行一个快速例程来检查新的可用版本。 If the version is available, we do the update and then would like to immediately restart the app.如果版本可用,我们会进行更新,然后想立即重新启动应用程序。 Since this is run before the main window appears to the user, it simply appears as though the app took a split second longer to start up.由于这是在主窗口出现在用户面前之前运行的,所以它看起来好像应用程序需要多一秒钟才能启动。
We're using Squirrel.Windows for our updater.我们使用Squirrel.Windows作为我们的更新程序。 I've made the class below to handle checking for/applying updates.我已经创建了下面的类来处理检查/应用更新。
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();
}
}
}
}
Unfortunately Squirrel has made their update process async
only, which means the CheckForUpdate
and UpdateApp
method must use await
, making the entire update method asynchronous.不幸的是,Squirrel 仅使他们的更新过程async
,这意味着CheckForUpdate
和UpdateApp
方法必须使用await
,从而使整个更新方法异步。 I assign the asnyc call to a Task
, then simply .Wait()
for the update to finish.我将 asnyc 调用分配给Task
,然后只需.Wait()
即可完成更新。
The problem comes when I try to restart my app.当我尝试重新启动我的应用程序时出现问题。 Based on what I've read, I need to use Dispatcher.Invoke
to call the restart due to the fact I am on a non-UI thread when performing the update.根据我所读到的内容,我需要使用Dispatcher.Invoke
来调用重新启动,因为我在执行更新时处于非 UI 线程上。 However, despite the code below, I still get the same error message:但是,尽管有下面的代码,我仍然收到相同的错误消息:
The Calling thread cannot access this object because a different thread owns it调用线程无法访问此对象,因为其他线程拥有它
Any idea how to correctly implement Dispatcher.Invoke
in order to restart the app?知道如何正确实现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编辑
Below is a screen shot of the Target
attribute of the Restart
action.下面是Restart
操作的Target
属性的屏幕截图。 The debugger was paused at the Restar?.Invoke
line from above.调试器在Restar?.Invoke
行从上面暂停。
Instead of trying to convert asynchronous programming to the old event based pattern, just use it properly.与其尝试将异步编程转换为旧的基于事件的模式,不如正确使用它。 You don't need events to detect when an asynchronous operation finished, nor do you need Invoke
to move back to the UI thread.您不需要事件来检测异步操作何时完成,也不需要Invoke
移回 UI 线程。 await
takes care of both. await
兼顾两者。
You could write code as simple as this:您可以编写像这样简单的代码:
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();
}
This is just enough code to extract to a separate class:这足以提取到一个单独的类:
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();
}
}
So the actual exception is somewhere in the Restart
handler is trying to access the MainWindow
get property from another thread based on the stack trace.所以实际的异常是在Restart
处理程序中的某个地方,它试图根据堆栈跟踪从另一个线程访问MainWindow
get 属性。 This is a complete guess, but I would store the original Dispatcher
in the OnStartup
method and use the stored Dispatcher in the Restart
event handler.这是一个完整的猜测,但我会将原始Dispatcher
存储在OnStartup
方法中,并在Restart
事件处理程序中使用存储的 Dispatcher。
Why you are not using SplashScreen
?为什么不使用SplashScreen
? This SplashScreen
would check for new versions, and either download updates, or start the old application.此SplashScreen
将检查新版本,并下载更新或启动旧应用程序。
A lovely tutorial to get you started : EASILY CREATE A WPF SPLASH SCREEN WITH STATUS UPDATES VIA MVVM一个让您入门的可爱教程: 通过 MVVM 轻松创建带有状态更新的 WPF启动画面
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.