3

私はMSASP.NETWebAPIクライアントライブラリHttpClientを使用しProgressMessageHandlerています。

私はこれをコンソールアプリケーションで問題なくいじくり回しましたが、WinFormアプリでは、「投稿」タスクがまたはのいずれかに固執するだけです。.Wait().Result

以下は、私の非常に単純なテストアプリケーションの完全なリストです。ボタン1は正常に機能し、ボタン2はへの呼び出しで毎回フリーズしpostTask.Resultます。なんで?

4.0または4.5をターゲットにしても違いはありません。コンソールアプリケーションの同じコードには問題はありません。

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Net.Http.Handlers;
using System.Windows.Forms;

namespace WindowsFormsApplication13
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private ProgressMessageHandler _progressHandler = new ProgressMessageHandler();
        private const string SERVICE_URL = "http://www.google.com.au";

        private HttpClient GetClient(bool includeProgressHandler = false)
        {
            var handlers = new List<DelegatingHandler>();

            if (includeProgressHandler)
            {
                handlers.Add(_progressHandler);
            }

            var client = HttpClientFactory.Create(handlers.ToArray());
            client.BaseAddress = new Uri(SERVICE_URL);
            return client;
        }

        private void PostUsingClient(HttpClient client)
        {
            var postTask = client.PostAsJsonAsync("test", new
            {
                Foo = "Bar"
            });

            var postResult = postTask.Result;

            MessageBox.Show("OK");
        }

        private void button1_Click(object sender, EventArgs e)
        {
            using (var client = GetClient())
            {
                PostUsingClient(client);
            }
        }

        private void button2_Click(object sender, EventArgs e)
        {
            using (var client = GetClient(true))
            {
                PostUsingClient(client);
            }
        }
    }
}

アップデート

OK、これが私の問題のようです。.NET 4.5の場合、明らかな解決策は、@ StephenClearyが示唆しているように、非同期/待機パターンをPostAsJsonAsync呼び出しからボタンクリックハンドラーまで浸透させることです。このようなもの:

private Task<HttpResponseMessage> PostUsingClient(HttpClient client)
{
    return client.PostAsJsonAsync("test", new
    {
        Foo = "Bar"
    });
}

private async void button2_Click(object sender, EventArgs e)
{
    var client = GetClient(true);
    var response = await PostUsingClient(client);
}

私の問題は、.NET 4.0で同等のソリューションを取得することです(もちろん、レガシーの理由で)。近い近似は、ボタンクリックハンドラーで継続を使用することです。問題は(これもレガシーの理由で)、非同期が戻るまでボタンクリックハンドラーをブロックすることです。4.0互換のyield演算子を使用していくつかの創造的な解決策を見つけましたが、それらは少し厄介な感じがします。代わりに、私が考案できる最も簡単な代替案は次のとおりです。

private void button2_Click(object sender, EventArgs e)
{
    var result = Task.Run(() => { return PostUsingClient(client); }).Result;
}

これが最もパフォーマンスの高い実装であるとは想像できませんが、それでも率直に言って不器用な感じがします。もっと上手くできますか?

4

2 に答える 2

4

同期コードと非同期コードを混在させようとすると、少し混乱することになります。(このデッドロックがWindowsフォームで発生するのにコンソールアプリでは発生しない理由を説明するブログ投稿があります)。

最善の解決策は、すべてを作成することasyncです。

(これもレガシーの理由で)、私は実際には、非同期が戻るまでボタンクリックハンドラーをブロックしたいと思っています。

いくつかの代替案を検討してください。クリックハンドラーの最初でボタンを無効にasyncし、最後で再度有効にすることは可能でしょうか?これはかなり一般的なアプローチです。

おそらく、ボタンクリックハンドラーをブロックする理由を説明する場合(別の質問で)、別の解決策を提案できます。

私が考案できる最も簡単な代替案は、[Task.Runを使用する]です。これが最もパフォーマンスの高い実装であるとは想像できませんが、それでも率直に言って不器用です。

それは他のどのソリューションよりも優れたソリューションです。呼び出し階層内のすべてのが使用されている場合は、直接呼び出すことできます。の問題の1つは、例外をでラップするため、エラー処理もより複雑になることです。ResultawaitPostUsingClientConfigureAwait(false)ResultAggregateException

于 2013-01-30T14:30:38.213 に答える
1

この問題を回避するために、次の関数を使用しました(レガシーのレトロフィットのみ)。

public T ExecuteSync<T>(Func<Task<T>> function) {
            return new TaskFactory(TaskScheduler.Default).StartNew((t) => function().Result, TaskContinuationOptions.None).Result; ;
        }

private void button2_Click(object sender, EventArgs e)
{
    var client = GetClient(true);
    var response = ExecuteSync(() => PostUsingClient(client));
}

これは、非同期のものを同期的に実行しようとするときに、常にThreadpool TaskSchedulerを使用して実行され、SyncronizationContextに基づくTaskSchedulerは実行されないことを保証するために機能します。発生している問題は、継続してGetAsyncを呼び出しているため、タスク内にあり、デフォルトではすべての新しいタスクが作成中のタスクのTaskSchedulerを継承することです。したがって、UIスレッドでGetAsyncを実行しようとしますが、UIスレッドをブロックすることになります。タスク内で実行していない場合、デフォルトの動作は、スレッドプールスケジューラであるデフォルトのタスクスケジューラを使用してGetAsyncを実行することです。(少なくともこれまでの私の理解です)

別の注意点として、リクエストごとに新しいHttpClientを作成したくない場合があります。アプリケーションスコープで作成するか、少なくともフォームスコープで作成して再利用します。HttpClientを破棄するたびに、基盤となるTCP接続を強制的に閉じようとし、HTTP1.1のデフォルトのキープアライブ動作の利点を効果的に排除します。

于 2013-01-30T15:08:29.757 に答える