2017-05-18 23 views
2

私はこれを数日間戦い続けてきましたが、それを把握することはできません。あなたの魔術師の1人が私にそれを説明できることを願っています。HttpClientがSendAsyncのデッドロックを待つ

これは、問題のコードです:

public async static Task<RemoteFileDetails> GetRemoteFileDetailsAsync(string strUrl, double dblTimeoutSeconds = 15.0) 
{ 
    try 
    { 
     using (var hc = new HttpClient()) 
     { 
      hc.Timeout = TimeSpan.FromSeconds(dblTimeoutSeconds); 
      using (var h = new HttpRequestMessage(HttpMethod.Head, strUrl)) 
      { 
       h.Headers.UserAgent.ParseAdd(UserAgent); 
       using (var rm = await hc.SendAsync(h)) 
       { 
        rm.EnsureSuccessStatusCode(); 
        return new RemoteFileDetails() 
        { 
         Url = strUrl, 
         FileName = rm.Content.Headers.ContentDisposition.FileName, 
         FileSize = rm.Content.Headers.ContentLength.GetValueOrDefault(), 
         LastModified = rm.Content.Headers.LastModified.GetValueOrDefault().LocalDateTime, 
         Valid = true 
        }; 
       } 
      } 
     } 
    } 
    catch (Exception ex) 
    { 
     System.Windows.MessageBox.Show(ex.ToString()); 
    } 
    return new RemoteFileDetails(); 
} 

このすべてがありませんされているURLにHEADリクエストに置き、特定のヘッダーの結果をつかみます。

ここでの問題は、デッドロック(約60秒間ポーズした後、TaskCanceledExceptionを投げた)の約50%であり、もう50%はすばらしく機能します。今、私は、ネットワークのログを追加し、これがデッドロックに何が起こっているかである:

System.Net Verbose: 0 : [2928] HttpWebRequest#3567399::HttpWebRequest(https://www.example.com/file.txt#-114720138) 
    System.Net Information: 0 : [2928] Current OS installation type is 'Client'. 
    System.Net Information: 0 : [2928] RAS supported: True 
    System.Net Verbose: 0 : [2928] Exiting HttpWebRequest#3567399::HttpWebRequest() 
    System.Net Verbose: 0 : [2928] HttpWebRequest#3567399::HttpWebRequest(uri: 'https://www.example.com/file.txt', connectionGroupName: '565144') 
    System.Net Verbose: 0 : [2928] Exiting HttpWebRequest#3567399::HttpWebRequest() 
    System.Net Verbose: 0 : [5456] HttpWebRequest#3567399::BeginGetResponse() 
    System.Net Verbose: 0 : [2244] HttpWebRequest#3567399::Abort() 
    System.Net Error: 0 : [2244] Exception in HttpWebRequest#3567399:: - The request was aborted: The request was canceled.. 
/////// THIS IS WHERE THE 30-60 SECOND PAUSE HAPPENS //////// 
    System.Net Error: 0 : [5456] Can't retrieve proxy settings for Uri 'https://www.example.com/file.txt'. Error code: 12180. 
    System.Net Verbose: 0 : [5456] ServicePoint#39086322::ServicePoint(www.example.com:443) 
    System.Net Information: 0 : [5456] Associating HttpWebRequest#3567399 with ServicePoint#39086322 
    System.Net Verbose: 0 : [2244] Exiting HttpWebRequest#3567399::Abort() 
    System.Net Verbose: 0 : [5456] HttpWebRequest#3567399::EndGetResponse() 
    System.Net Error: 0 : [5456] Exception in HttpWebRequest#3567399::EndGetResponse - The request was aborted: The request was canceled.. 
    System.Net Verbose: 0 : [5456] Exiting HttpWebRequest#3567399::BeginGetResponse() -> ContextAwareResult#36181605 

だから、配置されつつあるHttpClientを()オブジェクトのように見えますか?しかしどうですか?これを確認するために、コードを次のように変更しました:

private static HttpClient _hc; 
public async static Task<RemoteFileDetails> GetRemoteFileDetailsAsync(string strUrl, double dblTimeoutSeconds = 15.0) 
{ 
    _hc = new HttpClient(); 
    try 
    { 
     _hc.Timeout = TimeSpan.FromSeconds(dblTimeoutSeconds); 
     using (var h = new HttpRequestMessage(HttpMethod.Head, strUrl)) 
     { 
      h.Headers.UserAgent.ParseAdd(UserAgent); 
      using (var rm = await _hc.SendAsync(h)) 
      { 
       rm.EnsureSuccessStatusCode(); 
       return new RemoteFileDetails() 
       { 
        Url = strUrl, 
        FileName = rm.Content.Headers.ContentDisposition.FileName, 
        FileSize = rm.Content.Headers.ContentLength.GetValueOrDefault(), 
        LastModified = rm.Content.Headers.LastModified.GetValueOrDefault().LocalDateTime, 
        Valid = true 
       }; 
      } 
     } 
    } 
    catch (Exception ex) 
    { 
     System.Windows.MessageBox.Show(ex.ToString()); 
    } 
    return new RemoteFileDetails(); 
} 

しかし、まだデッドロックしています。

これはなぜ起こっているのですか?私はasync/awaitが基本的にルーチンを一時停止していると考えていたので、HttpClientは決して処理してはいけません。

ここではボートを完全に逃していますが、それは別のものです。ここで

は、例外のSCREENCAPです: enter image description here

ETA: 私はそれは私がこれを呼び出していますどのように示すことが重要だと仮定します。これはMVVM Lightを使用したWPFアプリケーションです。メインビューモデルでは、私はこれを次のように呼び出しています。

public MainViewModel(IDataService ds) 
    { 
     _dataService = ds; 
     this.Initialize(); 
    } 

    private async void Initialize() 
    { 
     var det = await RemoteFileUtils.GetRemoteFileDetailsAsync("https://example.com/file.txt"); 
} 
+0

'.ConfigureAwait(continueOnCapturedContext:false)'を試したことがありますか?私はあなたがUIスレッドからこの関数を直接呼び出すのだろうかと思います。 – xxbbcc

+0

@xxbbccはい、何も変わりません。昨日試しました。私はそれをどのように呼んでいるかを示すために質問を更新する。 – Andy

+0

@xxbbcc OK、私はそれをどのように呼んでいるかを示すために質問を更新しました。 – Andy

答えて

2

この問題は、どのように呼び出されているかに起因します。

呼び出し元クラスのコンストラクタで呼び出さないでください。 async voidがイベントハンドラ内にない場合は避けてください。

public class MainViewModel : ViewModelBase { 

    public MainViewModel(IDataService ds) { 
     _dataService = ds; 
     this.Initialize += OnInitialize; 
     //Un-comment if you want to call event immediately 
     //Initialize(this, EventArgs.Empty); 
    } 

    public event EventHandler Initialize; 

    private async void OnInitialize(object sender, EventArgs e) { 
     var det = await RemoteFileUtils.GetRemoteFileDetailsAsync("https://example.com/file.txt"); 
    } 

    //Or call this after class has created. 
    public void Ready() { 
     Initialize(this, EventArgs.Empty); 
    } 
} 

次は、要求を行うたびに新しいHttpClientを作成して削除しないようにしてください。 1つのインスタンスを作成し、それをアプリケーションのライフサイクル全体にわたって使用します。

keep an instance of HttpClient for the lifetime of your application for each distinct API that you connect to.

GetRemoteFileDetailsAsyncもむしろ静的にアクセスされるよりも注射サービスことがリファクタリングされるべきです。

+0

1つの質問:アプリケーションごとに1つのHttpClientしか持たないと言っています。同時に複数のスレッド間でHttpClientオブジェクトを使用できますか?または、HTTP転送が完了するまで、スレッド専用にHttpClientをロックする必要がありますか? – Andy

+1

@Sonic答えのリンクを確認してください。クライアントをロックする必要はありません。スレッドセーフです – Nkosi

+0

もう1つ質問です。上記の例では、 ''イベントを直ちに呼び出す場合はコメントを解除する 'と言っていますが、もしそれを行うと、待ち時間を使わずに 'async void'メソッドを呼び出すのと同じボートに戻ります? – Andy

関連する問題