2017-08-03 5 views
0

失います。あるブランチから別のブランチにコードをマージして、新しいブランチの一部のインテグレーションテストがSystem.NullReferenceExceptionのために失敗することがわかりました。私は両方のブランチで私たちのコードが同一であり、参照されているすべてのDLLも同じであることを保証するために時間を費やしています。すべてが同じようです。待望非同期タスクは、私たちはTFSに2つの支店を持っている非同期HTTP POSTをを作るWebアプリケーションに関して非常に奇妙な状況...</p> <p>を持ってHttpContext.Current

私はテストをデバッグすることにしました。

私たちのテストは、Mock IHttpClientオブジェクトを作成することです。 clientMock.PostAsyncWithResponseMessage(x、y)が新しいHttpResponseMessage()オブジェクト(さまざまなプロパティを設定している)を返すように、Mockオブジェクトをスタブします。

ので、コードは次のようになります。

using (var response = await client.PostAsyncWithResponseMessage(url, postData).ConfigureAwait(true)) 
{ 
    if (response.IsSuccessStatusCode) 
    { 
     ret.Response = await response.Content.ReadAsStringAsync().ConfigureAwait(true); 
     ret.ContentType = response.Content.Headers.ContentType.ToString(); 
    } 

    ret.StatusCode = response.StatusCode.ToInt(); 
} 

時に、このに行を取る:

await client.PostAsyncWithResponseMessage(url, postData).ConfigureAwait(true)) 

それは非同期メソッドのようにこれは見えるが、「クライアント」は、私たちのモックオブジェクトでありますIHttpClientインターフェイスをサポートするだけです。スレッドを検査すると、この行の実行時にIDは変更されません。

後で、我々が持っている:この行が実行されたとき

await response.Content.ReadAsStringAsync().ConfigureAwait(true); 

を今、HttpContext.Currentはnullに設定されている - 私たちのすべてのコンテキストがゴミ箱に移動されます。このアプリケーションでは、どこにでもConfigureAwait(false)を呼び出さないので、私たちがコンテキストを失うべき理由がないことはわかっています。

私はその行を変更した場合:

response.Content.ReadAsStringAsync().Result; 

次に、このコースのブロッキングのであり、我々はコンテキストを失うことはありません。

ので、2つの質問:。

  1. はなぜ()ConfigureAwait(真)のawaitメソッドは、コンテキストを失うことになりますか? [なぜなら、1つのTFSブランチの同じコードが別のブランチで作業しているのに失敗する理由を示唆してもいいと思うのですが、私はそれを期待していません。]
  2. .PostAsyncWithResponseMessage (url、postData)メソッドを使用していました。しかし、データを取得すると、ReadAsStringAsync()を待つのにどのような利点がありますか。つまり、ではなく、の.Result構文を使用するのがよい理由はありますか?あなたがスレッドを調べる場合は、この行を実行する際に、IDは変更されません

おかげ

+0

https://msdn.microsoft.com/en-us/magazine/jj991977.aspx – Nkosi

+0

@Nkosi:私はこの偉大な記事をよく知っており、宣言されているベストプラクティスに従っていると考えています。あなたが私が読んでいることを示唆しているこの記事の特定の部分を指摘してください。ありがとう – DrGriff

答えて

3

これは必ずしも同期して実行されたわけではありません。ほとんどの単体テストフレームワークはフリースレッドのコンテキスト(SynchronizationContext.Currentnull)を提供し、スレッドプールスレッド上でそれらのテストを実行します。コードが同じスレッドで再開することはまったく可能です(特に、1つのテストのみを実行している場合)。

この行が実行されると、HttpContext.Currentnullに設定されます。すべてのコンテキストがゴミ箱になっています。このアプリケーションでは、どこでもConfigureAwait(false)と呼ぶわけではありません。なぜなら、私たちがコンテキストを失うべき理由がないことはわかっています。

私はSynchronizationContext.Currentnullに設定されているので、が実際にコンテキストがないと仮定します。おそらく起きているのは、テストでHttpContext.Current(いくつかのランダムなスレッドプールスレッド)が設定されていて、最初の真の非同期操作の後に、Currentが設定されているスレッドでテストが再開することがあります。

HttpContext.Currentの伝播を処理するthere's a request contextSynchronizationContext)のASP.NET。単体テストに類似したものがない場合は、HttpContext.Currentを保存するコンテキストはありません。

クイックフィックスをお探しの場合は、おそらく私のAsyncContext typeを使用してください。

void TestMethod() 
{ 
    AsyncContext.Run(async() => 
    { 
    // Test method body goes here. 
    }); 
} 

:ASP.NETのコンテキストと同じセマンティクスではないが、いくつかのテストのために有用であるために十分類似した - これは、すべての非同期のコードが同じスレッドで再開されるように、シングルスレッドのコンテキストを提供しますサイドノート、ConfigureAwait(true)はちょうどノイズです。デフォルトの動作はtrueです。

.Result構文を使用しない理由はありますか?

あなたのコードから、サーバの応答が実際にはPostAsyncWithResponseMessageが完了するまでに読み取られることは明らかではありません。あなたの問題の説明(そしてあなたのテストでResultがそれを修正したという事実)に基づいて、実際にはReadAsStringAsyncが非同期で動作しているようです。

この場合、ブロックしない2つの理由があります.1)スレッドを不必要にブロックしている、2)自分自身を開封しているのがdeadlockです。

+1

あなたの記事は本当に助けて、私は問題を発見した。 TFSの両方のブランチで、HttpContext.Currentがnullに設定されていたことが判明しました。しかし、私たちはIOCコンテキスト(共通アセンブリ内に埋め込まれています)を使用し、シングルトンが共通アセンブリを共有する別のアプリケーションで[ThreadStatic]としてマークされていることがわかりました。このIOCはある支店ではnullに設定されていましたが、他の支店では設定されておらず、問題の原因です。サイドノートについて - ConfigureAwait(true)を追加すると、開発者が意図的に行った決定であったことがn年後にわかります(また、falseに設定しないでください)。ありがとう! – DrGriff

関連する問題