【问题标题】:Handle cancellation of async method处理异步方法的取消
【发布时间】:2014-01-21 02:26:08
【问题描述】:

我正在使用Parse 作为应用程序的数据存储,并且我正在实现他们的Facebook Login 功能。 AFAIK,此登录方法与其他异步方法没有任何不同,因此希望它适用。

所以有一个 Login.xaml 页面,其中有一个“使用 Facebook 登录”按钮,点击此按钮会将您带到 FacebookLogin.xaml 页面,该页面仅包含 WebBrowser 根据链接的 Parse 文档的控件。在 FacebookLogin.xaml 上的ContentPanel.Loaded 中,我可以使用以下代码登录:

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)
   {
   }
}

如果我真的登录了,它就可以工作,我可以将用户导航到下一页。当我让浏览器控件加载时出现问题(所以async方法是等待),然后点击Back按钮,然后返回再次访问 Facebook 登录页面

当我这样做时,会抛出一个OperationCancelledException,但我不知道如何处理它。我试过的:

  1. catchOperationCanceledException 中,将WebBrowser 控件重新初始化为一个新控件。这没有效果。

  2. 在同一个catch,再次调用FacebookLogin(),重试。这也没有奏效。

  3. 我也尝试过不使用 await 并返回 Task<ParseUser>,但我也不知道该怎么做。

我可以对取消异常做些什么,或者使用CancellationToken 来更好地处理这个问题?我只想正确处理取消操作,以便在用户按“返回”时重新加载 Facebook 登录页面。

解决方案:

好的,经过@JNYRanger 的大量指导,我想出了一个可行的解决方案。可能有更好的方法,但这似乎有效。这是我的 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);
   }
}

所以基本上,当按下“返回”时,我使用CancellationToken 请求取消,这会引发异常。发生异常时,我将task 设置为null。现在,当重新导航到 FacebookLogin 页面时(之前没有登录),task 可以成功重新创建。

【问题讨论】:

  • 小心,你仍然有 async void 不是事件处理程序的方法。
  • 我不确定如何在没有async void 的情况下使其工作,因为如果FacebookLogin() 返回Task&lt;ParseUser&gt;,我必须在FacebookLogin() 上使用await,这将要求我在ContentPanel_Loaded 上使用async,这给了我另一个async void
  • 实际上,根据您提供的文章,如果async 方法是一个事件处理程序(就像ContentPanel_Loaded 那样),那是可以的。那么只制作ContentPanel_Loadedasync会是一个更好的解决方案吗?
  • 没错。这样就行了。对于非事件处理程序,而不是将它们设为async void,您可以将它们设为async Task,其中任务不是通用的。由于您不需要 ParseUser,因此您不需要使用 Task&lt;ParseUser&gt; 而不是返回 void 您只返回一个未包装的 Task 对象,您可以等待它以防万一出现错误。
  • public async Task FacebookLogin()

标签: c# asynchronous windows-phone-8 async-await cancellationtokensource


【解决方案1】:

从来没有async void 方法。您无法正确处理其中的异常。如果您的 async 方法没有返回任何内容,请改用 async Task(非通用)作为返回类型。

然后您可以等待返回的Task 以正确处理异常。

如果您要设置使用CancellationTokens,请让您将CancellationTokenSource 的令牌传递给async 方法。然后,您可以将此令牌注册到回调或继续将令牌传递到 LoginAsync 方法(如果该重载可用)。我使用这个MSDN article 来熟悉取消方法。

还可以查看 MSDN 博客中关于避免 async void 问题的这篇文章:Async-Await Best Practices

编辑
根据您问题中的编辑,我想指出一些事情。如果您致电

Task<ParseUser> t = FacebookLogin()

你现在有一个Task 对象,你可以用它来做一些事情。但是,如果您只需要 ParseUser 对象并且没有延续或需要对 Task 执行任何其他操作,则应再次使用 await

ParseUser p = await FacebookLogin();

由于这是在事件处理程序(已加载)中,因此它是一个可以拥有async void 的异常

当您取消订单时会发生什么,这取决于您。您可以关闭登录窗口,取消其他任务/方法等。

【讨论】:

  • 感谢您提供的信息,请参阅我的更新。当取消异常发生时,我仍然对我真正需要做什么感到困惑?
  • 这取决于你想在方法被取消时发生什么。当方法被取消时你想发生什么?还要记住,当您调用 await 时,您得到的是包装的值,而不是任务本身。
  • 对,我不需要这个任务,只需要ParseUser,所以我以后可以使用它。因此,当我访问 FacebookLogin.xaml 时,浏览器会加载并且异步方法等待用户登录。如果用户没有登录,而是点击返回,然后再次返回 FacebookLogin.xaml 页面,则会发生取消异常。我不需要特别发生任何事情,我只需要正确处理任务,以便我可以再次尝试调用LoginAsync()
  • 这就是我感到困惑的地方。我可以捕获异常,但是如果我尝试从那里再次调用FacebookLogin(),我会立即收到另一个任务取消异常。就像页面或某事需要刷新。
  • 这可能是一个单独的问题。但是,此时最好取消整个登录并更改窗口,而不是自动重试。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-04-02
  • 2017-08-12
  • 2013-04-25
  • 1970-01-01
  • 1970-01-01
  • 2019-03-19
  • 1970-01-01
相关资源
最近更新 更多