0

I have a WPF application with a bunch of user controls, pages etc. When i deploy my app and run it, sometimes it will just crash and close. Very intermittent. I have to go to the Event viewer and look at the Windows logs to see what happened. I will put a condensed version of the error found in the logs:

 Application: MyApp.exe
Framework Version: v4.0.30319
Description: The process was terminated due to an unhandled exception.
  Exception Info: System.InvalidOperationException
   at System.ThrowHelper.ThrowInvalidOperationException(System.ExceptionResource)
   at System.Nullable`1[[System.TimeSpan, mscorlib, Version=4.0.0.0, Culture=neutral, 
  PublicKeyToken=b77a5c561934e089]].get_Value()
  at MyApp.UserControls.UserCountdown+<>c__DisplayClass13_0.<StartAnimation>b__0(

I see my function StartAnimation is where its bombing, but I have that code in a try catch block:

   public void StartAnimation()
    {
        try
        {
          //my code
        }
        catch (Exception ex){
           //gracefully handle this
        }
     }

The Windows logs error doesn't display a line number or anything. I'm not looking for coding, I am wanting to know how can i stop my application from just totally crashing and actually land in my try catch blocks instead of the Event Viewer?

P.S. Here's my entire function. Not sure if this will help:

public void StartAnimation()
    {
        try
        {

        double from = -90;
        double to = 270;
        int seconds = Seconds;
        int newSecs = seconds;
        if (Seconds > 60)
            newSecs = newSecs / 2;

        TimeSpan duration = TimeSpan.FromSeconds(Seconds);
        storyboard = new Storyboard();
        DoubleAnimation animation = new DoubleAnimation(from, to, new Duration(duration));

        Storyboard.SetTarget(animation, Arc);
        Storyboard.SetTargetProperty(animation, new PropertyPath("EndAngle"));

        storyboard.CurrentTimeInvalidated += (s, e) =>
        {
            int diff = (int)((s as ClockGroup).CurrentTime.Value.TotalSeconds);
            Seconds = seconds - diff;
            TimeSpan t = new TimeSpan(0, 0, Seconds);
            if (t.Hours > 0)
            {
                timeLabel.FontSize = 16;
                TimeDisplay = $"{t.Hours.ToString("D2")}:{t.Minutes.ToString("D2")}:{t.Seconds.ToString("D2")}";
            }
            else if (t.Minutes > 0)
            {
                timeLabel.FontSize = 25;
                TimeDisplay = $"{t.Minutes.ToString("D2")}:{t.Seconds.ToString("D2")}";
            }
            else
            {
                timeLabel.FontSize = 30;
                TimeDisplay = $"{t.Seconds.ToString("D2")}";
            }
        };

        storyboard.Children.Add(animation);
        storyboard.Begin();
        }
        catch (Exception ex)
        {
            Emailer.SendEmail($"Error calling StartAnimation - {ex.StackTrace}", ex.Message, "", "", "");
        }

    }
BoundForGlory
  • 4,114
  • 15
  • 54
  • 81
  • When you Step through in debugging what is the last line before the ionisation happens ? – jimjim Feb 11 '21 at 21:55
  • Red Herring Maybe, Check your 'seconds' and 'Seconds' - TimeSpan duration = TimeSpan.FromSeconds(Seconds); ??? – Traci Feb 11 '21 at 21:55
  • I'm assuming `Exception` in both code excerpts refers to `System.Exception` and not some other type? I know that's usually the case but since they don't appear to be catching all (normally catchable) exceptions, maybe they're actually catching something else? – Joe Sewell Feb 11 '21 at 21:59
  • 1
    The line with the issue is probably `int diff = (int)((s as ClockGroup).CurrentTime.Value.TotalSeconds);` and `CurrentTime` is probably `null` – Charlieface Feb 11 '21 at 22:05

1 Answers1

5

The reason you're running into this issue is that C# event handlers cannot safely throw exceptions. There is a great StackOverflow answer here which goes into more detail about why, but to paraphrase:

Throwing an exception from an event handler leaves the caller (triggering the event) with very few options about how to safely proceed while guaranteeing that other event handlers also receive the event.

In this case, it sounds like the class triggering the event (Storyboard) does not catch exceptions thrown by event handlers and instead allows the program to crash (which is, in some ways, the safest recourse).

Coming back to your code, you've got something which can broadly be simplified into the following structure:

try
{
  var storyboard = new Storyboard();

  storyboard.Event += (o, e) => {
    throw new Exception("Example");
  };

  storyboard.Begin();
}
catch (Exception)
{
  // Don't crash
}

The problem is that the try-catch block will only catch exceptions thrown by code executing within the call stack of that try-catch. The event handler itself may be executing on the call stack of another thread (in this case, most likely a thread in the ThreadPool) and is not within this try-catch block. This means that unless the Storyboard class catches exceptions from the event handler and then re-throws them within the scope of its Begin() method (which it isn't doing) you will experience an unhandled exception.

To solve this, you simply need to wrap the code inside your event handler in its own try-catch block like this:

storyboard.CurrentTimeInvalidated += (s, e) =>
{
  try
  {
    // TODO: Add your event handler code here
  }
  catch (Exception ex)
  {
    // TODO: Handle exceptions triggered within your animation here
  }
};
Benjamin Pannell
  • 3,645
  • 2
  • 19
  • 22