【问题标题】:Calling async method on button click单击按钮时调用异步方法
【发布时间】:2015-02-19 08:33:43
【问题描述】:

我创建了 Windows Phone 8.1 项目,我试图在按钮单击时运行 async 方法 GetResponse<T>(string url) 并等待方法完成,但方法永远不会完成。这是我的代码:

private void Button_Click(object sender, RoutedEventArgs 
{
      Task<List<MyObject>> task = GetResponse<MyObject>("my url");
      task.Wait();
      var items = task.Result; //break point here
}

public static async Task<List<T>> GetResponse<T>(string url)
{
    List<T> items = null;
    HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url);

    var response = (HttpWebResponse)await Task.Factory.FromAsync<WebResponse>(request.BeginGetResponse, request.EndGetResponse, null);
    try
    {
        Stream stream = response.GetResponseStream();
        StreamReader strReader = new StreamReader(stream);
        string text = strReader.ReadToEnd();
        items = JsonConvert.DeserializeObject<List<T>>(text);
    }
    catch (WebException)
    {
        throw;
    }
    return items;
}

它将挂在task.Wait()。

我将按钮单击方法更改为async,并在async 方法之前使用await,我得到了结果(await GetResponse&lt;string&gt;("url"))。 Task&lt;List&lt;string&gt;&gt; task = GetResponse&lt;string&gt;("url") 有什么问题? 我做错了什么?

感谢您的帮助!

【问题讨论】:

  • 请注意,一般约定async 方法以Async 为后缀,即您的方法名称应为GetResponseAsync。
  • 在这里调用task.Wait(); 是多余的。 task.Result 将在必要时调用 task.Wait()。

标签: c# wpf multithreading windows-phone-8 asynchronous


【解决方案1】:

您是经典僵局的受害者。 task.Wait() 或 task.Result 是 UI 线程中的阻塞调用,会导致死锁。

Don't block in the UI thread。永远不要这样做。就await吧。

private async void Button_Click(object sender, RoutedEventArgs 
{
      var task = GetResponseAsync<MyObject>("my url");
      var items = await task;
}

顺便说一句,你为什么抓住WebException 并把它扔回去?如果你根本不抓住它会更好。两者都是一样的。

我还可以看到您在 GetResponse 方法中混合了异步代码和同步代码。 StreamReader.ReadToEnd 是一个阻塞调用——你应该使用StreamReader.ReadToEndAsync。

还对返回任务或异步的方法使用“Async”后缀,以遵循 TAP(“基于任务的异步模式”)约定,如 Jon says。

解决上述所有问题后,您的方法应该如下所示。

public static async Task<List<T>> GetResponseAsync<T>(string url)
{
    HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url);
    var response = (HttpWebResponse)await Task.Factory.FromAsync<WebResponse>(request.BeginGetResponse, request.EndGetResponse, null);

    Stream stream = response.GetResponseStream();
    StreamReader strReader = new StreamReader(stream);
    string text = await strReader.ReadToEndAsync();

    return JsonConvert.DeserializeObject<List<T>>(text);
}

【讨论】:

    【解决方案2】:

    这就是杀死你的原因:

    task.Wait();
    

    这会阻塞 UI 线程,直到任务完成 - 但任务是一个异步方法,它将在“暂停”并等待异步结果后尝试返回 UI 线程。它不能那样做,因为你阻塞了 UI 线程......

    你的代码中没有任何东西看起来确实需要在 UI 线程上,但假设你真的确实想要它,你应该使用:

    private async void Button_Click(object sender, RoutedEventArgs 
    {
        Task<List<MyObject>> task = GetResponse<MyObject>("my url");
        var items = await task;
        // Presumably use items here
    }
    

    或者只是:

    private async void Button_Click(object sender, RoutedEventArgs 
    {
        var items = await GetResponse<MyObject>("my url");
        // Presumably use items here
    }
    

    现在不是阻塞直到任务完成,Button_Click 方法将在任务完成后安排一个继续触发后返回。 (这就是 async/await 的工作原理,基本上。)

    请注意,为了清楚起见,我还将 GetResponse 重命名为 GetResponseAsync。

    【讨论】:

    • 我已将async 添加到我的按钮单击事件处理程序中,并且await 正在执行异步任务,但异步任务仍在我的主线程上运行并阻塞了Windows 窗体UI。任何想法为什么当我遵循上面的例子时?谢谢
    • @ChrisWalsh:是的,异步任务会仍然在你的主线程上运行。如果它真的是异步的(例如做异步 IO),那应该没问题。如果该任务执行阻塞操作,那不很好。我建议你用一个完整的例子提出一个新问题。
    【解决方案3】:

    使用下面的代码

     Task.WaitAll(Task.Run(async () => await GetResponse<MyObject>("my url")));
    

    【讨论】:

    • Task.WaitAll 将像 task.Wait 一样阻止。
    • 问题是“按钮单击并等待方法完成”所以上面的代码片段将等到方法完成。
    • 这正是我所需要的……谢谢。我有一个用户点击一个按钮..那个按钮做了不同的事情......其中一个可能最终需要调用一个 Asyc 进程。需要知道如何停止.. 允许该过程完成,以便我当前的线程可以继续进行下一步。完美运行!
    猜你喜欢
    • 2023-03-31
    • 1970-01-01
    • 2019-01-10
    • 2016-02-11
    • 2019-08-25
    • 1970-01-01
    • 2019-11-09
    • 2020-05-16
    • 1970-01-01
    相关资源
    最近更新 更多