web-dev-qa-db-ja.com

ボタンのクリック時に非同期メソッドを呼び出す

Windows Phone 8.1プロジェクトを作成し、ボタンクリックで非同期メソッドGetResponse(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()でハングします。

ボタンクリックメソッドを非同期に変更し、非同期メソッドの前にawaitを使用して結果を取得しました(await GetResponse<string>("url"))。 Task<List<string>> task = GetResponse<string>("url")の何が問題になっていますか?私は何を間違えていますか?

助けてくれてありがとう!

44
Doniyor Niyozov

あなたは古典的なデッドロックの犠牲者です。 task.Wait()またはtask.Resultは、デッドロックを引き起こすUIスレッドのブロッキング呼び出しです。

Iスレッドでブロックしない 。絶対にしないでください。ちょっと待って。

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

ところで、なぜWebExceptionをキャッチして、それを投げ返すのですか?単純に捕まえない方がいいでしょう。両方とも同じです。

また、GetResponseメソッド内で非同期コードと同期コードを混合していることがわかります。 StreamReader.ReadToEndはブロッキング呼び出しです。StreamReader.ReadToEndAsyncを使用する必要があります。

また、 Jon says のようにTAP( "Task based Asynchronous Pattern")規約に従うためにTaskまたは非同期を返すメソッドに "Async"サフィックスを使用します。

上記のすべての懸念に対処したら、メソッドは次のようになります。

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);
}
79

これはあなたを殺しているものです:

task.Wait();

これは、タスクが完了するまでUIスレッドをブロックしますが、タスクは非同期メソッドであり、「一時停止」して非同期結果を待機した後、UIスレッドに戻ろうとします。 UIスレッドをブロックしているため、それはできません...

とにかくUIスレッド上にある必要があるように見えるコードには何もありませんが、本当にdoが必要だと仮定して、使用する必要があります:

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
}

現在、タスクが完了するまでblockingの代わりに、Button_Clickメソッドは、タスクの完了時に実行を継続するようにスケジューリングした後に戻ります。 (これが基本的にasync/awaitの仕組みです。)

わかりやすくするために、GetResponseの名前もGetResponseAsyncに変更します。

25
Jon Skeet