[解決済み] タスクのResultプロパティにアクセスしようとすると、なぜこの非同期アクションがハングアップするのですか?
質問
多階層の .Net 4.5 アプリケーションで、C# の新しいメソッドである
async
と
await
というキーワードでハングアップしてしまうのですが、その理由がわかりません。
一番下にデータベースユーティリティを拡張する非同期メソッドがあります。
OurDBConn
を拡張する非同期メソッドを持っています (基本的に、基礎となる
DBConnection
と
DBCommand
オブジェクトを含む)。
public static async Task<T> ExecuteAsync<T>(this OurDBConn dataSource, Func<OurDBConn, T> function)
{
string connectionString = dataSource.ConnectionString;
// Start the SQL and pass back to the caller until finished
T result = await Task.Run(
() =>
{
// Copy the SQL connection so that we don't get two commands running at the same time on the same open connection
using (var ds = new OurDBConn(connectionString))
{
return function(ds);
}
});
return result;
}
次に、これを呼び出す中レベルの非同期メソッドを持っていて、ゆっくり実行される集計を得ることができます。
public static async Task<ResultClass> GetTotalAsync( ... )
{
var result = await this.DBConnection.ExecuteAsync<ResultClass>(
ds => ds.Execute("select slow running data into result"));
return result;
}
最後に同期的に実行されるUIメソッド(MVCアクション)を用意しました。
Task<ResultClass> asyncTask = midLevelClass.GetTotalAsync(...);
// do other stuff that takes a few seconds
ResultClass slowTotal = asyncTask.Result;
問題は、その最後の行で永遠にハングアップすることです。同じことを
asyncTask.Wait()
. 遅いSQLメソッドを直接実行すると、4秒くらいかかります。
私が期待している動作は、このメソッドが
asyncTask.Result
に到達したとき、まだ終了していなければ終了するまで待ち、終了したら結果を返すというものです。
デバッガで実行すると、SQL文は完了し、ラムダ関数も終了します。
return result;
の行は
GetTotalAsync
の行に到達することはありません。
何が間違っているのか、何か思い当たることはありますか?
これを解決するために、どこを調査する必要があるか、何か提案はありますか?
これはどこかでデッドロックになっている可能性があり、もしそうなら、それを見つける直接的な方法はありますか?
どのように解決するのですか?
はい、これはデッドロックです。TPLではよくあることなので、気にしないでください。
と書くと
await foo
と書くと、ランタイムはデフォルトで、メソッドが開始されたのと同じSynchronizationContext上で関数の継続をスケジュールします。英語で、あなたが
ExecuteAsync
をUIスレッドから呼び出したとします。あなたのクエリはスレッドプールスレッドで実行されます(あなたが
Task.Run
を呼び出したからです)、そしてあなたは結果を待ちます。これは、ランタイムがあなたの "
return result;
行をスレッドプールにスケジューリングするのではなく、UIスレッドに戻して実行することを意味します。
では、どのようにデッドロックが発生するのでしょうか。このコードがあると想像してください。
var task = dataSource.ExecuteAsync(_ => 42);
var result = task.Result;
つまり、最初の行は非同期処理を開始するものです。次に2行目
はUIスレッドをブロックします。
. 従って、ランタイムが "return result" 行を UI スレッドに戻して実行しようとするとき、それは
Result
が完了するまで実行できません。しかし、もちろん、returnが起こるまでResultを与えることはできません。デッドロックです。
これは、TPLを使用する際の重要なルールを示しています。
.Result
をUIスレッド(または他の派手な同期コンテキスト)で使用する場合、Taskが依存するものがUIスレッドにスケジュールされていないことを確実にするよう注意する必要があります。さもなければ、邪悪なことが起こります。
では、どうすればいいのでしょうか?選択肢その1は、どこでもawaitを使うことですが、あなたが言ったように、それはすでに選択肢ではありません。2つ目の選択肢は、awaitを使わないことです。2つの関数を次のように書き換えることができます。
public static Task<T> ExecuteAsync<T>(this OurDBConn dataSource, Func<OurDBConn, T> function)
{
string connectionString = dataSource.ConnectionString;
// Start the SQL and pass back to the caller until finished
return Task.Run(
() =>
{
// Copy the SQL connection so that we don't get two commands running at the same time on the same open connection
using (var ds = new OurDBConn(connectionString))
{
return function(ds);
}
});
}
public static Task<ResultClass> GetTotalAsync( ... )
{
return this.DBConnection.ExecuteAsync<ResultClass>(
ds => ds.Execute("select slow running data into result"));
}
何が違うのでしょうか?今はどこにも待ち受けがないので、UIスレッドに暗黙のうちにスケジュールされるものは何もありません。このような単一の戻り値を持つ単純なメソッドでは、"を行う意味はありません。
var result = await...; return result
パターン; async修飾子を削除して、タスクオブジェクトを直接渡すだけです。非同期修飾子を外してタスクオブジェクトを直接渡せばいいのです。
オプション3は、待ち行列をUIスレッドにスケジュールするのではなく、スレッドプールにスケジュールするよう指定することです。これを行うには
ConfigureAwait
メソッドで行います。
public static async Task<ResultClass> GetTotalAsync( ... )
{
var resultTask = this.DBConnection.ExecuteAsync<ResultClass>(
ds => return ds.Execute("select slow running data into result");
return await resultTask.ConfigureAwait(false);
}
タスクの待ち受けは、通常 UI スレッドにいる場合はそのスレッドにスケジュールされます。
ContinueAwait
の結果を待つ場合、あなたが今いるコンテキストは無視され、常にスレッドプールにスケジューリングされます。この欠点は、この
をあちこちに散りばめなければならないことです。
を散りばめなければならないということです。なぜなら、あなたの.Resultが依存するすべての関数で、見逃した
.ConfigureAwait
は別のデッドロックの原因となる可能性があるからです。
関連
-
[解決済み] エンティティタイプ ApplicationUser は、現在のコンテキストのモデルの一部ではありません。
-
[解決済み】値が期待した範囲に収まらない
-
[解決済み】Unity 「関連するスクリプトを読み込むことができません」「Win32Exception: システムは指定されたファイルを見つけることができません"
-
[解決済み】Visual Studio: 操作を完了できませんでした。パラメータが正しくありません
-
[解決済み】ランダムなブーリアンを生成する最速の方法
-
[解決済み] C#でawaitを使わずに非同期メソッドを安全に呼び出す方法
-
[解決済み】非同期プログラミングとマルチスレッドの違いは何ですか?
-
[解決済み】非同期処理の待ち時間、Wait()でプログラムがフリーズする原因はここにある
-
[解決済み] 非同期アクションのデリゲートメソッドはどのように実装するのですか?
-
[解決済み] 警告 CS1998 を抑制する。この非同期メソッドには 'await' がありません。
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】WebForms UnobtrusiveValidationModeは、jqueryのScriptResourceMappingを必要とする
-
[解決済み] エンティティタイプ <type> は、現在のコンテキストのモデルの一部ではありません。
-
[解決済み】「namespace x already contains a definition for x」エラーの修正方法は?VS2010にコンバートした後に発生しました。
-
[解決済み】非静的メソッドはターゲットを必要とする
-
[解決済み】C# - パスに不正な文字がある場合
-
[解決済み] [Solved] 不正な文字列値: '\xEFxBFxBD' for column
-
[解決済み】エラー「必要なフォーマルパラメータに対応する引数が与えられていない」を解決する?
-
[解決済み] 関数を終了するには?
-
[解決済み】名前 'ViewBag' が現在のコンテキストに存在しない - Visual Studio 2015
-
[解決済み】スレッド終了またはアプリケーションの要求により、I/O操作が中断されました。