【问题标题】:Why does this WPF app fail to exit correcty?为什么此 WPF 应用程序无法正确退出?
【发布时间】:2013-11-19 18:15:26
【问题描述】:

我在 WPF 应用程序中有以下代码,当一个长时间运行的进程发生时显示启动画面。在我们所有的开发机器和测试机器上,这都能完美运行。然而,在一些客户机器上,这段代码让主进程继续运行。

我尝试了各种调用关闭的方法,包括 Environment.Exit(0);,但我们仍然看到该进程在完成后仍在运行。

关于我的任务和应用程序的交互方式,我是否遗漏了什么?

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Diagnostics;
using System.IO.Pipes;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;

namespace GKUpdate
{
  /// <summary>
  /// Interaction logic for App.xaml
  /// </summary>
  public partial class App : Application
  {
    protected override void OnStartup(StartupEventArgs e)
    {
      MainWindow oWindow;
      string sPipeName;
      string sGKPath;

      //Call base startup
      base.OnStartup(e);

      //Find the GK path
      sPipeName = FindArgument(e.Args, "n");
      sGKPath = FindArgument(e.Args, "p");

      //Check if we have a path
      if (!string.IsNullOrEmpty(sGKPath))
      {
        //Start listening
        Task.Factory.StartNew(() => ListenForSuccess(sPipeName, sGKPath));

        //Show the splash window
        oWindow = new MainWindow();
        oWindow.Show();
      }
      else
      {
        //Exit
        this.Shutdown();
      }
    }

    private string FindArgument(string[] oArgs, string sArgumentName)
    {
      string sFilter;
      string sArgument;

      //Get the argument
      sFilter = string.Format("/{0}=", sArgumentName).ToLower();
      sArgument = oArgs.FirstOrDefault(x => x.ToLower().StartsWith(sFilter));

      //Check if we found the argument
      if (!string.IsNullOrEmpty(sArgument) && sArgument.Length > sFilter.Length)
      {
        //Set the argument
        sArgument = sArgument.Substring(sFilter.Length).Trim('"');
      }
      else
      {
        //Set null
        sArgument = null;
      }

      //Return the argument
      return sArgument;
    }

    private void ListenForSuccess(string sPipeName, string sGKPath)
    {
      int iStatus;

      try
      {
        //Set default status
        iStatus = -1;

        //Loop until the service is online
        do
        {
          //Create the named pipe
          using (NamedPipeClientStream oNamedPipe = new NamedPipeClientStream(".", sPipeName, PipeDirection.InOut))
          {
            //Connect the pipe allowing 5 mins
            oNamedPipe.Connect(300000);

            //Send the byte asking for a status report
            oNamedPipe.WriteByte(0);
            oNamedPipe.WaitForPipeDrain();

            //Read the return
            iStatus = oNamedPipe.ReadByte();

            //Disconnect
            oNamedPipe.Close();
          }
        } while (iStatus != 1);

        //Check if we can do the success actions
        if (iStatus == 1)
        {
          //Start GateKeeper using the remaining command arguments
          Process.Start(sGKPath, string.Join(" ", Environment.GetCommandLineArgs().Skip(3)));
        }
      }
      catch (Exception)
      {
        //Do nothing
      }
      finally
      {
        //Exit the application
        Application.Current.Dispatcher.InvokeShutdown();
      }
    }
  }
}

【问题讨论】:

  • 你使用过DispatcherUnhandledException="Application_DispatcherUnhandledException"
  • 为 OnExit 添加一个覆盖并检查您是否到达该代码。
  • @JRB - 如果不处理DispatcherUnhandledException,我难道不希望我的应用程序在这样的异常情况下退出吗?
  • @eranotzap - 正如我所说,这段代码在 Visual Studio 以及所有开发和测试机器上都能完美运行。
  • 如果iStatus 永远不会变成 1 怎么办?

标签: c# wpf process task-parallel-library


【解决方案1】:

检查Threads 窗口是visual studio。当您的应用程序关闭时,您的 非后台 线程之一没有运行完成。我希望您此时仍在“倾听”。

您如何处理这取决于您,但我建议实施task cancellation

【讨论】:

  • 监听函数作为任务运行,所以它不应该在后台线程上吗?此外,应用程序仅从侦听线程关闭,因此它将完成。
  • @Stevo3000 这真的取决于“//在这里做等待逻辑”的情况。你在听什么?那可以开始一个线程吗?是否有需要调用的 EndInvoke 方法?再次检查你的线程窗口。
  • “//Do waiting logic here”部分是打开一个端口并等待一个肯定的响应。
  • @Stevo3000 什么样的端口?串行、TCP 等?如果您想要 to-the-letter 修复,则需要显示该代码。
【解决方案2】:

可能有多种原因。首先你必须检查窗口事件查看器,你才能找到真正的原因。

你也应该处理 DispatcherUnhandledException="Application_DispatcherUnhandledException"。这将显示实际错误。

在 App.XAML 中:

DispatcherUnhandledException="Application_DispatcherUnhandledException"

在 App.cs 中:

private void Application_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
{

    e.Handled = true;
}

【讨论】:

  • 文档说如果我们不处理DispatcherUnhandledException,那么应用程序将退出一个。由于这是所需的行为,我看不出处理此异常有什么帮助?另外,我不确定调度员会出现什么问题? MainWindow 窗口没有代码,它只是一个闪屏。
【解决方案3】:

您的后台线程被阻塞,等待管道连接。您需要使用 oNamedPipe.Close() 从前台线程关闭管道。正如 Erno de Weerd 所说,您还需要确保在管道中止后可以退出 do/while 循环。

更好的方法是将 CancellationToken 传递给任务,并在前台线程请求取消时使用它来关闭管道。然后,您还可以检查循环中的取消状态。

【讨论】:

  • 我在一些早期的代码中有一个取消令牌,但期望的行为是让用户无法取消任务。我知道令牌是最佳做法,但我不确定它如何解决我的眼前问题?
  • 用户可以关闭应用程序,但这并不会取消任务,这真的没有意义。 CancellationToken 不是用户的东西,它只是一种信号取消的方式,无论是来自用户还是来自其他(关闭)代码。
  • 好的,我同意,但请记住,Connect() 调用运行时间很长,因此使用令牌需要等待调用完成。目前,由于任务是后台任务,它会在应用程序关闭时被杀死。
  • 不,不会。这就是我在回答中的意思。如果您执行 cancelToken.Register(() => oNamedPipe .Close());当您创建管道时,前台线程将在设置令牌时关闭管道,并且您长时间运行的 Connect() 调用将立即中止
  • 我已经实现了这个,但我仍然认为它对我的问题没有帮助。
【解决方案4】:

请参阅How to force Task.Factory.StartNew to a background thread? 将 Task.Factory.StartNew 标记为后台线程,以便在所有“前台”线程停止执行后立即停止线程:

Task.Factory.StartNew(动作, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default).ContinueWith(completeAction);

【讨论】:

    猜你喜欢
    • 2016-11-23
    • 1970-01-01
    • 2014-12-11
    • 1970-01-01
    • 1970-01-01
    • 2011-11-25
    • 2010-09-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多