0

I have the following code in a WPF application that shows a splash screen while a long running process happens. On all of our developer machines and testing machines this works perfectly. However on some customer machines this code is leaving the main process running.

I've tried various methods of calling a shutdown including Environment.Exit(0); and we are still seeing this process left running after it has completed.

Is there something that I have missed about how my task and the application are interacting?

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();
      }
    }
  }
}
stevehipwell
  • 56,138
  • 6
  • 44
  • 61
  • 1
    have you use DispatcherUnhandledException="Application_DispatcherUnhandledException" – J R B Nov 19 '13 at 08:54
  • add an override to OnExit and check if you reach that code. – eran otzap Nov 19 '13 at 08:55
  • @JRB - By not handling `DispatcherUnhandledException` wouldn't I expect my application to exit on such and exception? – stevehipwell Nov 19 '13 at 09:00
  • @eranotzap - As I've stated, this code works perfectly in Visual Studio and on all development and testing machines. – stevehipwell Nov 19 '13 at 09:01
  • What if `iStatus` never becomes 1 ? – Emond Nov 19 '13 at 09:18
  • @ErnodeWeerd - Then it will stay running with an on top window until the user closes the window. At which point the application should shut down. – stevehipwell Nov 19 '13 at 09:28
  • If you want a thread to shutdown when the window closes you should mark it as a background thread. See: http://stackoverflow.com/questions/15957164/how-to-force-task-factory-startnew-to-a-background-thread – Emond Nov 19 '13 at 10:12
  • @ErnodeWeerd - I think the important part of that answer is `TaskScheduler.Default`. By default tasks run on a background thread. – stevehipwell Nov 19 '13 at 10:39
  • @Stevo3000 - Yes but it looks more like an implementation feature than a documented way to mark a task's thread as a background thread... So it might break on a future version of the scheduler. – Emond Nov 19 '13 at 11:24
  • You might create a thread by hand and mark it as background thread as you are not really using the Task API's features as far as I can see – Emond Nov 19 '13 at 11:25

4 Answers4

0

Check the Threads window is visual studio. One of your non-background threads is not running to completion when your application closes. I expect that you are still 'listening' at this point.

How you handle this is up to you but i recommend implementing task cancellation.

Gusdor
  • 14,001
  • 2
  • 52
  • 64
  • The listen function is running as a task so shouldn't it be on a background thread? Also the application only closes from the listen thread so it will be finished. – stevehipwell Nov 19 '13 at 09:04
  • @Stevo3000 this really depends what is going on at "//Do waiting logic here". What are you listening on? Could that be starting a thread? Is there an `EndInvoke` method that you need to call? Again, check your threading window. – Gusdor Nov 19 '13 at 09:07
  • The "//Do waiting logic here" part is opening a port and waiting for a positive response. – stevehipwell Nov 19 '13 at 09:10
  • @Stevo3000 What kind of port? Serial, TCP, etc? You need to show that code if you want a _to-the-letter_ fix. – Gusdor Nov 19 '13 at 09:12
0

There can be multiple reason. first you have to check window Event Viewer, you will able to find actual reason.

also you should handle DispatcherUnhandledException="Application_DispatcherUnhandledException". this will show actual error.

in App.XAML :

DispatcherUnhandledException="Application_DispatcherUnhandledException"

and in App.cs:

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

    e.Handled = true;
}
J R B
  • 2,069
  • 4
  • 17
  • 32
  • The documentation says that if we don't handle `DispatcherUnhandledException` then the application will exit on one. As this is the desired behaviour I don't see how handling this exception will help? Plus I'm not sure what could go wrong with the dispatcher? The `MainWindow` window has no code it is just a splash screen. – stevehipwell Nov 19 '13 at 09:27
0

Your background thread is blocked waiting for the pipe to connect. You need to close the pipe from the foreground thread with an oNamedPipe.Close(). As Erno de Weerd says, you also need to make sure you can exit your do/while loop once the pipe aborts.

A nicer way would be to pass in a CancellationToken to the task, and use that to close the pipe when the foreground thread requests cancellation. You can then also check the cancellation state in your loop.

GazTheDestroyer
  • 20,722
  • 9
  • 70
  • 103
  • I had a cancellation token in some earlier code but the desired behaviour is for the user to not be able to cancel the task. I know that a token would be best practice but I'm not sure how it would solve my immediate problem? – stevehipwell Nov 19 '13 at 09:32
  • I doesn't really make sense that the user can close the app but this doesn't cancel the task. A CancellationToken is not a user thing, it's just a way of signalling cancellation, whether from the user or from other (shutdown) code. – GazTheDestroyer Nov 19 '13 at 09:36
  • OK I agree, but remember that the `Connect()` call is long running so using a token would require waiting for the call to complete. Currently as the task is background it is killed when the application is shut down. – stevehipwell Nov 19 '13 at 09:46
  • 1
    No it wouldn't. That's what I mean in my answer. If you do a cancelToken.Register(() => oNamedPipe .Close()); when you create your pipe, the foreground thread will close the pipe when it sets the token, and your long running Connect() call will abort immediately – GazTheDestroyer Nov 19 '13 at 10:00
  • I've implemented this but I still don't think it will help my problem. – stevehipwell Nov 19 '13 at 10:41
0

See How to force Task.Factory.StartNew to a background thread? to mark a Task.Factory.StartNew as a background thread so the thread is stopped as soon as all 'foreground' threads have stopped execution:

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

Community
  • 1
  • 1
Emond
  • 50,210
  • 11
  • 84
  • 115