Search code examples
c#xamarin.formscancellation

Can't catch exception from ThrowIfCancellationRequested()


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


Solution

  • 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.