「待つ」は機能しますが、タスクを呼び出します。結果がハング/デッドロックします。


126

次の4つのテストがあり、最後のテストを実行するとハングします。なぜこれが起こるのですか?

[Test]
public void CheckOnceResultTest()
{
    Assert.IsTrue(CheckStatus().Result);
}

[Test]
public async void CheckOnceAwaitTest()
{
    Assert.IsTrue(await CheckStatus());
}

[Test]
public async void CheckStatusTwiceAwaitTest()
{
    Assert.IsTrue(await CheckStatus());
    Assert.IsTrue(await CheckStatus());
}

[Test]
public async void CheckStatusTwiceResultTest()
{
    Assert.IsTrue(CheckStatus().Result); // This hangs
    Assert.IsTrue(await CheckStatus());
}

private async Task<bool> CheckStatus()
{
    var restClient = new RestClient(@"https://api.test.nordnet.se/next/1");
    Task<IRestResponse<DummyServiceStatus>> restResponse = restClient.ExecuteTaskAsync<DummyServiceStatus>(new RestRequest(Method.GET));
    IRestResponse<DummyServiceStatus> response = await restResponse;
    return response.Data.SystemRunning;
}

私はこの拡張メソッドをrestsharp RestClientに使用します:

public static class RestClientExt
{
    public static Task<IRestResponse<T>> ExecuteTaskAsync<T>(this RestClient client, IRestRequest request) where T : new()
    {
        var tcs = new TaskCompletionSource<IRestResponse<T>>();
        RestRequestAsyncHandle asyncHandle = client.ExecuteAsync<T>(request, tcs.SetResult);
        return tcs.Task;
    }
}
public class DummyServiceStatus
{
    public string Message { get; set; }
    public bool ValidVersion { get; set; }
    public bool SystemRunning { get; set; }
    public bool SkipPhrase { get; set; }
    public long Timestamp { get; set; }
}

なぜ最後のテストがハングするのですか?


7
非同期メソッドからvoidを返さないようにする必要があります。これは、既存のイベントハンドラーとの下位互換性のためだけのものであり、主にインターフェイスコードにあります。非同期メソッドが何も返さない場合は、Taskを返す必要があります。MSTestで多数の問題があり、非同期テストが返されませんでした。
ghord 2013年

2
@ghord:MSTestはasync void単体テストメソッドをまったくサポートしていません。それらは単に機能しません。ただし、NUnitにはあります。とはいえ、私はを優先async Taskするという一般原則に同意しasync voidます。
スティーブンクリアリー

@StephenClearyはい。ただし、VS2012のベータ版では許可されていたため、あらゆる種類の問題が発生していました。
ghord 2013年

回答:


88

私のブログMSDNの記事で説明している標準のデッドロック状態に陥っていますasyncメソッドは、への呼び出しによってブロックされているスレッドに継続をスケジュールしようとしていResultます。

この場合、SynchronizationContextNUnitがasync voidテストメソッドを実行するために使用するのはあなたです。async Task代わりにテストメソッドを使用してみます。


4
非同期タスクへの変更が機能しました。リンクの内容を数回読む必要があります。
Johan Larsson

@MarioLopez:解決策は、async(私のMSDNの記事で述べたように)「ずっと」使用することです。言い換えると、私のブログ投稿のタイトルにあるように、「非同期コードをブロックしないでください」。
Stephen Cleary 2016

1
@StephenClearyコンストラクタ内で非同期メソッドを呼び出さなければならない場合はどうなりますか?コンストラクターを非同期にすることはできません。
Raikol Amaro

1
@StephenCleary SOに関するほとんどすべての返信と記事で、あなたが話しているのを目Wait()にするのは、呼び出しメソッドを作成することで置き換えることだけですasync。しかし、私には、これが問題を上流に押し上げているようです。ある時点で、何かを同期的に管理する必要があります。関数が長時間実行されるワーカースレッドを管理するため、意図的に同期している場合はどうなりTask.Run()ますか?NUnitテスト内でデッドロックせずに完了するのをどのように待つのですか?
void.pointer

1
@ void.pointer:At some point, something has to be managed synchronously.-まったくありません。UIアプリの場合、エントリポイントはasync voidイベントハンドラーにすることができます。サーバーアプリの場合、エントリポイントはasync Task<T>アクションにすることができます。asyncスレッドのブロックを回避するために、両方に使用することをお勧めします。NUnitテストを同期または非同期にすることができます。非同期の場合は、のasync Task代わりにしてくださいasync void。同期している場合は、同期SynchronizationContextが必要ないため、デッドロックは発生しません。
スティーブンクリアリー

222

非同期メソッドを介して値を取得する:

var result = Task.Run(() => asyncGetValue()).Result;

非同期メソッドを同期的に呼び出す

Task.Run( () => asyncMethod()).Wait();

Task.Runを使用しても、デッドロックの問題は発生しません。


15
async void単体テストメソッドの使用を奨励し、テスト対象SynchronizationContextのシステムからによって提供される同じスレッドの保証を削除する場合は-1 。
Stephen Cleary

68
@StephenCleary:非同期voidの「奨励」はありません。デッドロックの問題を解決するために、有効なc#構成を採用しているだけです。上記のスニペットは、OPの問題に不可欠でシンプルな回避策です。Stackoverflowは問題の解決策であり、詳細な自己宣伝ではありません。
Herman Schoenfeld、2015年

81
@StephenCleary:あなたの記事はソリューションを明確に表現していません(少なくとも明確ではありません)。また、ソリューションがあったとしても、そのような構成要素を間接的に使用します。私のソリューションは明示的にコンテキストを使用しないので、何ですか?ポイントは、私の作品であり、それはワンライナーです。問題を解決するために2つのブログ投稿と数千の単語を必要としませんでした。注:私はasync voidも使用していないので、あなたが何をしているのか本当にわかりません..簡潔で適切な答えのどこかに「async void」が表示されていますか?
Herman Schoenfeld、2015年

15
@HermanSchoenfeld、方法理由を追加した場合、私はあなたの答えが多くの利益になると信じています。
ironstone13

19
これはちょっと遅いのはわかっていますが、ラップされないように.GetAwaiter().GetResult()代わりに使用する必要があります。.ResultException
Camilo Terevinto 2017

15

ConfigureAwait(false)この行にデッドロックが追加されるのを回避できます。

IRestResponse<DummyServiceStatus> response = await restResponse;

=>

IRestResponse<DummyServiceStatus> response = await restResponse.ConfigureAwait(false);

私は私のブログの記事で、この落とし穴を説明してきた非同期/のawaitの落とし穴


9

Task.Resultプロパティを使用してUIをブロックしています。でMSDNのドキュメント彼らは、それを明確に言及しています

Resultプロパティはブロッキングプロパティです。タスクが完了する前にアクセスしようとすると、現在アクティブなスレッドは、タスクが完了して値が利用可能になるまでブロックされます。ほとんどの場合、Awaitを使用して値にアクセスする必要があります。または待つ宿泊施設に直接アクセスするのではなく。」

このシナリオの最善の解決策は、メソッドからawaitとasyncの両方削除し、結果を返すタスクのみを使用することです。それはあなたの実行シーケンスを台無しにしないでしょう。


3

コールバックを取得しないか、コントロールがハングアップした場合、サービス/ API非同期関数を呼び出した後、同じ呼び出されたコンテキストで結果を返すようにContextを構成する必要があります。

使用する TestAsync().ConfigureAwait(continueOnCapturedContext: false);

この問題はWebアプリケーションでのみ発生し、では発生しませんstatic void main


ConfigureAwait元のスレッドコンテキストで実行しないことにより、特定のシナリオでのデッドロックを回避します。
davidcarr
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.