1

Welp, I've this code:

    public static async Task TimedSync (CancellationToken ct)
    {
        try {
            if (ct.IsCancellationRequested)
                ct.ThrowIfCancellationRequested();
            await Task.Run(async () => await UP.Sincronizacao.SyncDB(true));
            Xamarin.Forms.Device.StartTimer(TimeSpan.FromMinutes(1), () => {
                if (ct.IsCancellationRequested)
                    ct.ThrowIfCancellationRequested();
                Task.Run(async () => await UP.Sincronizacao.SyncDB(false));
                return true;
            });
        } catch (OperationCanceledException) {
            await Current.MainPage.DisplayAlert("Got it", "Good", "ok");
        } catch (Exception e) {
            await Current.MainPage.DisplayAlert("Oops", e.Message, "dismiss");
        }
    }

The app just crashes at this point, and on debug I find that the exception thrown by ThrowIfCancellationRequested() is unhandled.

Edit: Ok, something really weird happened, I removed the first if(ct.IsCancellationRequested) ct.ThrowIfCancellationRequested(); and followed Peter's suggestion, the Throw inside the lambda now throws the exception, the try catch block I put on it as well didn't work, but the try catch outside the lambda caught the exception. Here's the code:

    public static async Task TimedSync (CancellationToken ct)
    {
        try {
            await Task.Run(async () => await UP.Sincronizacao.SyncDB(true));
            Xamarin.Forms.Device.StartTimer(TimeSpan.FromMinutes(1), () => {
                try {
                    if (ct.IsCancellationRequested)
                        ct.ThrowIfCancellationRequested();
                    Task.Run(async () => await UP.Sincronizacao.SyncDB(false));
                    return true;
                } catch (OperationCanceledException) {
                    return false;
                }
            });
        } catch (OperationCanceledException) {
            await Current.MainPage.DisplayAlert("Got it", "Good", "ok");
        } catch (Exception e) {
            await Current.MainPage.DisplayAlert("Oops", e.Message, "dismiss");
        }
    }

It kinda works for me :) But still would like to understand what's going on here

  • What specifically is the exception that is unhandled? – Joe Sewell Jan 22 '19 at 16:40
  • OperationCanceledException precisely – Theodor3gabriel Jan 22 '19 at 16:41
  • Oh, I didn't notice you have two `ThrowIfCancellationRequested` calls. Is it the second one that's throwing the exception? `Xamarin.Forms.Device.StartTimer` looks like it's a fire-and-forget function, so exceptions from its delegate won't propagate out to the outer method. – Joe Sewell Jan 22 '19 at 16:44
  • Are you sure the exception is thrown in the thread where you have your `catch`? I would say, that the problem is, that you need to catch the exception in another thread *(since I see there `async`, `Taks.Run`, `StartTimer`)*. – Julo Jan 22 '19 at 16:44
  • 1
    If you catch `CancellationException` *inside* the lambda passed to `StartTimer` and return false, that should fix it. – Peter Wishart Jan 22 '19 at 16:47
  • Actually it was the first, then I commented it and it ends up happening on the second one. And yes, Visual Studio told me on debug it was the `ThrowIfCancellationRequested ` line, that's why I'm so lost – Theodor3gabriel Jan 22 '19 at 16:47
  • Ok Peter, I'll try – Theodor3gabriel Jan 22 '19 at 16:48
  • No repro. I bet you think you're running against this version of your code when you're running against another version. Here's my repro https://gist.github.com/WillSullivan/6f550f45db2b3f0c7674804c7390052b compare this to your code and see if I'm missing something (sorry, knocked this out in a min) –  Jan 22 '19 at 16:55
  • you can review this [stackoverflow link](https://stackoverflow.com/questions/12633903/elegantly-handle-task-cancellation) – Fredy Adriano Jimenez Martinez Jan 22 '19 at 17:01

1 Answers1

1

You are passing StartTimer a lambda that will throw a CancellationException when cancellation happens, but this exception doesn't necessarily fire inside StartTimer or the scope of TimedSync.

My guess, because I don't use Xamarin, is that the timer code running your lambda sees the exception on a separate task and promotes that to an application failure.

If you catch CancellationException in the lambda and return false this should have the desired effect of stopping the timer without propagating an exception to the Xamarin timer code.

Note that the direct call to ct.ThrowIfCancellationRequested() will be caught inside TimedSync and hit your catch block.

Peter Wishart
  • 11,600
  • 1
  • 26
  • 45