简体   繁体   English

从非 UI 线程重新启动 WPF 应用程序

[英]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 ,这意味着CheckForUpdateUpdateApp方法必须使用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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM