Pergunta

I'm using Parse as a data store for an app, and I am implementing their Facebook Login functionality. AFAIK, this Login method isn't any different than other async methods so hopefully it applies.

So there is a Login.xaml page, that has a button for "Login with Facebook", and tapping this button takes you to the FacebookLogin.xaml page which contains only the WebBrowser control as per the linked Parse documenation. In ContentPanel.Loaded on FacebookLogin.xaml, I can use the following code to log in:

async void FacebookLogin()
{
   try
   {
      user = await ParseFacebookUtils.LogInAsync(fbBrowser, new[] { "user_likes", "email" });
   }
   catch (OperationCanceledException oce)
   {
      // task was cancelled, try again
      //task = null;
      //FacebookLogin();
   }
   catch (Exception ex)
   {
   }
}

If I actually log in, it works, and I can navigate the user to the next page. The problem happens when I let the browser control load (so the async method is waiting), and then hit the Back button, and then come back to the Facebook Login page again.

When I do this, an OperationCancelledException is thrown, but I'm not sure how to handle it. What I've tried:

  1. In the catch for the OperationCanceledException, set re-initialize the WebBrowser control to a new one. This had no effect.

  2. In the same catch, call FacebookLogin() again, to retry. This also didn't work.

  3. I've also tried not using await and returning a Task<ParseUser> but I wasn't sure how to go about doing that either.

Is there something I can do with the cancellation exception, or use a CancellationToken to handle this better? I just want to properly handle the cancellation so that I can re-load the Facebook Login page if the user presses "Back".

SOLUTION:

Ok, after much guidance from @JNYRanger, I have come up with a working solution. There may be a better way, but this seems to work. Here is the entire code from my FacebookLogin.xaml:

public partial class LoginFacebook : PhoneApplicationPage
{
   Task<ParseUser> task;
   CancellationTokenSource source;

   public LoginFacebook()
   {
      InitializeComponent();
      ContentPanel.Loaded += ContentPanel_Loaded;
   }

   async void ContentPanel_Loaded(object sender, RoutedEventArgs e)
   {
        try {
          source = new CancellationTokenSource();
          task = ParseFacebookUtils.LogInAsync(fbBrowser, new[] { "user_likes" }, source.Token);
          await task;

          // Logged in! Move on...
       }
       catch (Exception ex) { task = null; }     
   }

   protected override void OnBackKeyPress(System.ComponentModel.CancelEventArgs e)
   {
       if (task != null) source.Cancel(false);
       base.OnBackKeyPress(e);
   }
}

So basically, when "Back" is pressed, I use the CancellationToken to request a cancel, which throws an exception. When the exception occurs, I set task to null. Now, when re-navigating to the FacebookLogin page (without previously logging in), the task can be successfully recreated.

Foi útil?

Solução

Never have async void methods. You cannot handle exceptions properly in them. If your async method does not return anything use async Task (not generic) as the return type instead.

You can then await on your returned Task to handle the exceptions properly.

If you are going to set use CancellationTokens make your you pass your CancellationTokenSource's token to the async method. You can then register this token either to a callback or continue to pass the token into the LoginAsync method if that overload is available. I used this MSDN article to get myself familiar with the cancellation methods.

Also take a look at this article from the MSDN blog in regards to avoiding the async void issues: Async-Await Best Practices

EDIT
Per the edit in your question I wanted to point something out. If you call

Task<ParseUser> t = FacebookLogin()

You now have a Task object that you can do stuff with. However, if you just want the ParseUser object and have no continuations or need to do anything else with the Task you should use await once again.

ParseUser p = await FacebookLogin();

Since this is in an event handler (loaded) it is the one exception where it's OK to have an async void

As to what happens when you a cancellation occurs, well that's up to you. You can close the login window, cancel other tasks/methods, etc.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top