0

I have written an WPF application which does some processing on the background. When the application closes, I want to wait until the processing is finished before the process stops. This code runs perfectly when I close the application by closing the form. But when I leave the application open end log off out of my windows session or reboot the machine the application seems to terminate on the SemaphoreSlim.Wait(). The strange thing is no errors are logged in the event log. I tried capturing the error with try catch, but the error is still uncaught.

Here is my code:

  public partial class NotifyWindow : Window
  {
    StreamWriter _stream;

    public NotifyWindow()
    {
      InitializeComponent();

      _stream = File.AppendText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "log.txt"));

      WriteLine("startup");
    }

    private void WriteLine(string line)
    {
      var text = string.Format("{0}    {1}", DateTime.Now, line);

      lock (_stream)
      {
        _stream.WriteLine(text);
        _stream.Flush();
      }
    }

    SemaphoreSlim _locker = new SemaphoreSlim(0);

    private void CloseMe()
    {
      WriteLine(string.Format("CloseMe started"));
      Thread.Sleep(1000);

      WriteLine(string.Format("release lock"));
      _locker.Release();

      WriteLine(string.Format("CloseMe finished"));
    }

    protected override void OnClosing(CancelEventArgs e)
    {
      WriteLine(string.Format("OnClosing started"));

      base.OnClosing(e);

      var t = new Thread(CloseMe);
      t.Start();

      WriteLine("Waiting on lock.");
      _locker.Wait();
      WriteLine("Waiting on lock finished.");

      WriteLine(string.Format("OnClosing finished"));
    }
  }

This results in the the following log

When application is closed by closing the window
24-4-2017 13:57:29    startup
24-4-2017 13:57:30    OnClosing started
24-4-2017 13:57:30    Waiting on lock.
24-4-2017 13:57:30    CloseMe started
24-4-2017 13:57:31    release lock
24-4-2017 13:57:31    CloseMe finished
24-4-2017 13:57:31    Waiting on lock finished.
24-4-2017 13:57:31    OnClosing finished

When application is closed by windows logoff
24-4-2017 13:57:43    startup
24-4-2017 13:57:47    OnClosing started
24-4-2017 13:57:47    Waiting on lock.
24-4-2017 13:57:47    CloseMe started

Is there a way I can wait for the code on the thread to finish before closing the application? I tried various (How to wait for thread to finish with .NET?) thread wait alternative but all seem to exhibit the same behavior.

I am using .net 4.6.2 and I reproduced the problem on windows 7 and windows 10.

TIA,

Bart Vries

Community
  • 1
  • 1
DesDesDes
  • 139
  • 2
  • What does your thread do? If it's used to write messages to a file, you can replace it completely either with a logging library or an `ActionBlock` that appends text to a file. You can use the `ActionBlock.Complete()` method when you close your application or the `SessionEnding` event fires. The result will be a lot cleaner – Panagiotis Kanavos Apr 25 '17 at 13:05
  • I have rewritten the code using an ActionBlock. The problem remains the same as the application not terminates on ActionBlock.Completion.Wait() when we you logoff from windows (closing the form does wait correctly, but windows shutdown does not). – DesDesDes Apr 25 '17 at 14:46
  • *Did* you call `Complete()` on `SessionEnding`? Did you remove the semaphore or anything else that would prevent the block from working? You don't need to use `.Wait()`, you can change the event handler's signature to `async void` and write `await block.Completion;`, eg `block.Complete(); await block.Completion;`. – Panagiotis Kanavos Apr 25 '17 at 15:15
  • @PanagiotisKanavos tried it all but still not working. Same problem exist as in the code above. When mail thread seems to lock while windows logoff is in progress it just kills the application even if the wait is only 1 second. – DesDesDes Jul 15 '17 at 22:36

3 Answers3

3

There is an Application.SessionEnding event that occurs when the user ends the Windows session by logging off or shutting down the operating system. You could try to handle this one.

You application cannot really control what the operating system does though. The user may still kill your application before your code has finisihed and there is not much you can do about this I am afraid.

mm8
  • 163,881
  • 10
  • 57
  • 88
  • I fried moving the code from the OnClosing to Application.SessionEnding but the problem remains the same. If still stops processing on _locker.Wait(); – DesDesDes Apr 25 '17 at 14:25
  • @DesDesDes the semaphore won't get signaled by itself. *Your code* has to signal it in `SessionEnding`. You don't have to move your code inside the event hanlder – Panagiotis Kanavos Apr 25 '17 at 15:17
  • @PanagiotisKanavos Just try the code for yourself and you will see that somehow the windows logoff notices the mail application thread is waiting on a semaphore and just kills the application without writing anything in the event log. – DesDesDes Jul 15 '17 at 22:34
  • 1
    @DesDesDes you missed the point that's adequately explained by mm8. You *can't* force the OS to work in a specific way. You *can't* block the OS - `SessionEnding` is meant for your application to perform some quick cleanup, within a fixed time period. It's *NOT* meant to block the OS, nor will it. If the cleanup takes too long, the OS will shut down the app. If the app is blocked, the OS notices (of course, it's the OS, semaphores are provided by *it*) and knows that your code will violate alloted cleanup time – Panagiotis Kanavos Jul 17 '17 at 07:09
1

The problem is you call base.close first in event close you should cancel the close of the form When the thread job is done you calll this.close but this time you set the parameter exit to true ( define parameter)

Vahid Vakily
  • 306
  • 1
  • 7
  • The problem is not caused by the base.close because then it would be reproducible when closing the application by closing the form and that still works. The CancelEventArgs.Cancel is ignored when the application is shutting down by a logoff. This is documented here: https://msdn.microsoft.com/en-us/library/system.windows.window.closing(v=vs.110).aspx So even if that was the problem is would not be fixable by setting the Cancel. – DesDesDes Apr 25 '17 at 14:29
0

Thank you all for your feedback. Based on your feedback I found a workaround by not using any OS locking mechanisms. This seems to be working.

Here is the code:

public partial class NotifyWindow : Window
{
  StreamWriter _stream;
  volatile int _running = 0;

  public NotifyWindow()
  {
    InitializeComponent();

    _stream = File.AppendText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "log.txt"));

    WriteLine("startup");
  }

  private void WriteLine(string line)
  {
    var text = string.Format("{0}    {1}", DateTime.Now, line);

    lock (_stream)
    {
      _stream.WriteLine(text);
      _stream.Flush();
    }
  }

  private void CloseMe()
  {
    WriteLine(string.Format("CloseMe started"));
    Thread.Sleep(1000);

    WriteLine(string.Format("release lock"));
    _running = 1;

    WriteLine(string.Format("CloseMe finished"));
  }

  protected override void OnClosing(CancelEventArgs e)
  {
    WriteLine(string.Format("OnClosing started"));

    base.OnClosing(e);

    var t = new Thread(CloseMe);
    t.Start();

    WriteLine("Waiting on lock.");

    while(_running == 0)
    {
      Thread.Sleep(50);
      WriteLine("Waiting for 50ms.");
    }

    WriteLine("Waiting on lock finished.");

    WriteLine(string.Format("OnClosing finished"));
  }
}

Thanks you all for your help.

DesDesDes
  • 139
  • 2