0

I am using Modern UI for WPF template in a .NET 4.0 app where one page needs to execute an async command before it navigates back to another page. At the same time, UI thread must be unlocked while command is running. If I do this:

    public void OnNavigatingFrom(FirstFloor.ModernUI.Windows.Navigation.NavigatingCancelEventArgs e)
    {
        TaskEx.Run(() =>
            {
                //Replace Sleep call by the async command execution
                System.Threading.Thread.Sleep(5000);
            }).Wait();}

The OnNavigatingFrom waits before navigating back, but the UI is blocked during this time.

Any ideas on how to execute the async code in a different context and make OnNavigatingFrom runs "synchronously"?

EDIT: There is a similar thread with a workaround and no conclusive answer.

Community
  • 1
  • 1
Igor Kondrasovas
  • 1,479
  • 4
  • 19
  • 36
  • 1
    You do realize Wait is a blocking call? – William Xifaras Nov 30 '16 at 19:52
  • I do and I know this is a problem. So how can I make it non blocking considering the calling method (OnNavigatingFrom) cannot return until operation is completed? – Igor Kondrasovas Nov 30 '16 at 21:02
  • If *(and only if)* the `OnNavigatingFrom` method is an event handler, you can set it as a `public async void` (thanks to reading a lot of literature by Stephen Cleary). Which will allow you to use `await`. E.g. `await DoSomethingAsync();`. The calling method will not return until the awaited task is complete – Geoff James Nov 30 '16 at 21:12
  • Might be of some use?: http://blog.stephencleary.com/2012/02/async-and-await.html – Geoff James Nov 30 '16 at 21:14
  • @GeoffJames You are right when you say the calling method will not return until the task is complete. However, the navigation is executed immediately, without waiting for the function returns. I think the reason is that Modern UI for WPF template frame navigation does not wait for completion. Any ideas? – Igor Kondrasovas Dec 01 '16 at 09:49
  • @IgorKondrasovas, it appears you have here two mutually exclusive requirements: 1) `OnNavigatingFrom` must not return until the task has completed 2) The UI shouldn't be blocked. Think about it this way: if the UI isn't blocked, what kind of UI actions would you allow a user to take (while the task is still pending)? Can the user continue using menus, etc? Or exit the app? The only way to "marry" these two requirements together without blocking is to introduce a nested message loop, but this is almost never a good idea. Related: http://stackoverflow.com/a/20891625/1768303. – noseratio Dec 01 '16 at 10:58
  • @IgorKondrasovas -- with the template/API you're using, is it possible to have an "OnNavigatingBack" event or similar? How exactly are you navigating back? Is there your own back button with a command that you execute to do this? Or is it a hardware back/built-in back button that you have no control over? – Geoff James Dec 01 '16 at 12:18
  • @IgorKondrasovas the correct pattern in your case is Commands. A command that executes an operation then navigates to a specific page upon completion. If you want to hand-code this, your View should be calling a method in your ViewModel or code-behind that executes the operation and *then* navigates to a new page – Panagiotis Kanavos Dec 01 '16 at 16:08
  • @PanagiotisKanavos I am curently working on exactly this approach. First I cancel the navigation, start my async command that will show some visuals to notify user about the operation progress. When the command is complete, I use an injected NavigationService in my viewmodel to navigate back (not cancelling at this time). – Igor Kondrasovas Dec 02 '16 at 13:14

4 Answers4

2

Here is what you need to do:

public void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
    // Fire and forget.
    Task.Run(() => MyCommandAsync());
}

The above is a fire and forget approach, instead it is preferred to use async and await:

public async void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
    await MyCommandAsync();
}
David Pine
  • 23,787
  • 10
  • 79
  • 107
  • 1
    This does not work, because OnNavigatingFrom is an event handler and simply adding the async on the method header will not make any differenct. The page will navigate back immediately. – Igor Kondrasovas Nov 30 '16 at 20:58
  • 1
    @IgorKondrasovas that's a matter of using the correct event. Using `await` is the correct way to await an asynchronous operation. You are simply using the wrong event/pattern. Instead of *navigating* you should be using a command that navigates to the new page upon completion – Panagiotis Kanavos Dec 01 '16 at 16:07
  • The first form will immediately post the execution of `MyCommandAsync` to the thread pool and return while the second will run on the UI thread until it reaches the first incomplete awaitable. – Paulo Morgado Dec 01 '16 at 23:33
2

one page needs to execute an async command before it navigates back to another page

This is the problem. User interfaces are not asynchronous, period. You can't just stick an asynchronous operation into the mix while the UI is doing a transition.

Think about it this way: as a user, if you decide to navigate back, and there's a network hiccup or something and the program freezes for a few seconds while navigating back, that's a bad user experience, right? The UI simply can't wait for asynchronous work during an update.

What you can do is do the work before the user is allowed to navigate back. You can, for example, capture the user's intent to navigate back and show some kind of "please wait" UI while the operation is going, and then after the operation completes do the actual navigation.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • What you suggest is what I am doing. The problem is that this "navigate back" method (OnNavigatingFrom) my page executes does not wait for async operations, where the "please wait" UI would be shown while operation is going. What I end up doing is cancel the navigation, do my async stuff and then navigate back from my ViewModel when operation is complete. – Igor Kondrasovas Dec 22 '16 at 13:39
  • @IgorKondrasovas: Yes, that is what you have to do. `OnNavigatingFrom` *can't* wait - it's not supposed to wait - it doesn't make sense to try to make it wait. – Stephen Cleary Dec 22 '16 at 15:55
0

Just await the Task instead of using .Wait().

See Using async-await on .net 4 for how to await in .NET 4.0

Community
  • 1
  • 1
sellotape
  • 8,034
  • 2
  • 26
  • 30
  • await the task does not work, because the calling method will not wait for completion before navigating back. – Igor Kondrasovas Nov 30 '16 at 20:59
  • 1
    Using await means the event handler will not return until the awaited task is complete, but the UI will be free to continue on. Using Wait() (or any other blocking mechanism) will block the UI. You can't have it both ways. Perhaps you can set a flag after the await finishes, which other code in the UI thread detects? – sellotape Nov 30 '16 at 21:03
  • Otherwise can't you just do what you want to do after the await finishes in the event handler? – sellotape Nov 30 '16 at 21:17
0

Use OnNavigatingFrom to do your stuff. e.Cancel prevent going back. Use Frame.GoBack() after your operation has finished.

protected override async void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
    if (Current.HasModifications())
    {
        e.Cancel = true;
        await Current.Save().ContinueWith((t) =>
        {
            //Go Back after the Operation has finished
            Frame.GoBack();
        }, TaskScheduler.FromCurrentSynchronizationContext());
    }
    else
    {
        base.OnNavigatingFrom(e);
    }
}
Enny
  • 759
  • 5
  • 8