Search code examples
c#async-awaituwpwindows-community-toolkitcommunity-toolkit-mvvm

UWP CommunityToolkit Messenger async handler


I have a UWP project and I want to message passing between ViewModel and View with CommunityToolkit MVVM Toolkit.

As you can see in my ViewModel we have something like below

        private async void CallbackClick()
        {
            while (true)
            {
                CallbackDto callback = callbackQueue.Pop();
                if (callback is null)
                    break;

                callbackQueue.Push(new CallbackDto(callback.Number, DateTimeOffset.UtcNow.ToUnixTimeSeconds()));
                string normalizedAddress = callback.Number;
                BSN.LinphoneSDK.Call outgoingCall = await LinphoneManager.Instance.NewOutgoingCall($"{normalizedAddress}");
                await outgoingCall.WhenEnded();
                // TODO: It is mandatory for backing to Dialer from InCall, but it is very bugous and must fix it
                await Task.Delay(1000);
                Task<CancellationToken> cancellationTokenTask = WeakReferenceMessenger.Default.Send<ContinueCallbackAnsweringRequestMessage>();
                CancellationToken cancellationToken = await cancellationTokenTask;
                if (cancellationToken.IsCancellationRequested)
                    break;
            }
        }

And I have register on message in Dialer.xaml.cs in NavigatedTo like below

 WeakReferenceMessenger.Default.Register<ContinueCallbackAnsweringRequestMessage>(this, async (r, message) =>
            {
                var continueCallbackAnsweringDialog = new MessageDialog("آیا مایل به ادامه پاسخ‌دهی به تماس‌های درخواستی هستید؟");
                TaskCompletionSource<CancellationToken> tcs = new TaskCompletionSource<CancellationToken>(TaskCreationOptions.RunContinuationsAsynchronously);
                continueCallbackAnsweringDialog.Commands.Add(new UICommand(
                    "بلی",
                    new UICommandInvokedHandler((IUICommand command) =>
                    {
                        tcs.SetResult(new CancellationToken(false));
                    })));
                continueCallbackAnsweringDialog.Commands.Add(new UICommand(
                    "خیر",
                    new UICommandInvokedHandler((IUICommand command) =>
                    {
                        tcs.SetResult(new CancellationToken(true));
                    })));
                continueCallbackAnsweringDialog.DefaultCommandIndex = 0;
                continueCallbackAnsweringDialog.CancelCommandIndex = 1;
                await continueCallbackAnsweringDialog.ShowAsync();

                message.Reply(tcs.Task);
            });

In this scenario I got a InvalidOperationException on WeakReferenceMessenger.Default.Send. My question is how to handle this situation?

In another side, if I remove async and await keyword in handler, my code is work correctly. but the problem is I do not await on IAsyncOperation and I want to await on in it.

I add discussion on CommunityToolkit GitHub about that.


Solution

  • My question is how to handle this situation?

    Since the MessageHandler won't (can't) be awaited by the toolkit, it returns when you call await ....

    You could try something like this:

    WeakReferenceMessenger.Default.Register<ContinueCallbackAnsweringRequestMessage>(
        this, (r, m) => m.Reply(ShowDialogAndWaitForResult()));
    

    ...where ShowDialogAndWaitForResult() is a custom async method that returns a Task<T>, calls the ShowAsync() of the dialog and wait for the result.

    Another option is implement your own blocking (non-async) dialog. Or consider another solution which don't involve a messenger.