【问题标题】:Restart WPF Application from non-UI thread从非 UI 线程重新启动 WPF 应用程序
【发布时间】:2016-04-06 16:49:54
【问题描述】:

在我的 WPF 应用程序中,我需要在启动时运行一个快速例程来检查新的可用版本。如果版本可用,我们会进行更新,然后希望立即重新启动应用程序。由于这是在用户看到主窗口之前运行的,所以它看起来好像应用程序需要多一秒钟才能启动。

我们使用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();
            }
        }
    }
}

不幸的是,Squirrel 的更新过程只有async,这意味着CheckForUpdateUpdateApp 方法必须使用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();

编辑

下面是Restart 操作的Target 属性的屏幕截图。调试器从上面的Restar?.Invoke 行暂停。

【问题讨论】:

  • 代码在哪里运行,是在您的 Program.cs 中还是在 Application 事件中?
  • 没有 Program.cs 文件,它是应用程序的 OnStartup 方法。这是程序运行的第一个命令。
  • 您可能会考虑添加带有Main 方法的Program.cs(如控制台应用程序),将其设置为您的启动对象(在属性-> 应用程序下),然后从那里进行更新。这样您就可以简单地选择在需要更新时不启动主窗口。
  • 很遗憾,此时无法更改启动过程。
  • 根据您在此处显示的代码和您说代码在OnStartup() 方法中执行的评论,我希望程序会死锁,而不是引发异常。事实上,当我试图重现这个问题时,这正是发生的事情(即死锁)。请提供一个好的minimal reproducible example,它可以可靠地重现问题。请注意,当您使用第三方库时,似乎没有任何关于该问题本身的特定内容;您应该能够简单地通过模拟该库的操作来重现该问题。

标签: c# wpf multithreading dispatcher


【解决方案1】:

与其尝试将异步编程转换为旧的基于事件的模式,不如正确使用它。您不需要事件来检测异步操作何时完成,也不需要Invoke 回到 UI 线程。 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();
    }
}

【讨论】:

    【解决方案2】:

    所以实际的异常是在Restart 处理程序的某个地方,它试图根据堆栈跟踪从另一个线程访问MainWindow get 属性。这是一个完整的猜测,但我会将原始 Dispatcher 存储在 OnStartup 方法中,并在 Restart 事件处理程序中使用存储的 Dispatcher。

    【讨论】:

      【解决方案3】:

      为什么你不使用 SplashScreen ?这个SplashScreen 将检查新版本,并下载更新或启动旧应用程序。

      一个可爱的教程让你开始:EASILY CREATE A WPF SPLASH SCREEN WITH STATUS UPDATES VIA MVVM

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-09-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-03-23
        相关资源
        最近更新 更多