3

entity-framework 6(コードファースト)+ WPFを使用して非同期待機プログラミングを試していますが、コードを非同期にした後もUIがフリーズする理由がわかりません。これが私が最初の行から行っていることです:

まず、クリックボタンに応答するイベントハンドラーがあります。

private async void LoginButton_Click(object sender, RoutedEventArgs e) {
  if (await this._service.Authenticate(username.Text, password.Password) != null)
    this.Close();
}

次に、サービスレイヤーにAuthenticateメソッドがあります。

public async Task<User> Authenticate(string username, string password) {
  CurrentUser = await this._context.GetUserAsync(username.ToLower().Trim(), password.EncryptPassword());
  return CurrentUser;
}

そして最後に、コンテキスト内のEFコードがあります。

public async Task<User> GetUserAsync(string username, string password) {
  return await this.People.AsNoTracking().OfType<User>().FirstOrDefaultAsync(u => u.Username == username && u.Password == password);
}

更新: UIのフリーズの原因を追跡した後、初期化プロセスであることが判明しました。UIスレッドは、EFコンテキストが初期化されるまでブロックされ、初期化されると、実際のクエリ/保存プロセスが非同期で実行されます。

クリックハンドラーの開始時にTask.Yield()を呼び出した後、デバッグ出力を更新します。

53:36:378 Calling Task.Yield
53:36:399 Called Task.Yield
53:36:400 awaiting for AuthenticateAsync
53:36:403 awaiting for GetUserAsync
'MyApp.vshost.exe' (Managed (v4.0.30319)): Loaded 'C:\Windows\Microsoft.Net\assembly\GAC_32\System.Transactions\v4.0_4.0.0.0__b77a5c561934e089\System.Transactions.dll', Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
'MyApp.vshost.exe' (Managed (v4.0.30319)): Loaded 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Numerics\v4.0_4.0.0.0__b77a5c561934e089\System.Numerics.dll', Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
'MyApp.vshost.exe' (Managed (v4.0.30319)): Loaded 'C:\Windows\Microsoft.Net\assembly\GAC_32\System.Data.OracleClient\v4.0_4.0.0.0__b77a5c561934e089\System.Data.OracleClient.dll', Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
'MyApp.vshost.exe' (Managed (v4.0.30319)): Loaded 'D:\SkyDrive\Works\MyApp\MyApp.UI.WPF.Shell\bin\Debug\EntityFramework.SqlServer.dll'
'MyApp.vshost.exe' (Managed (v4.0.30319)): Loaded 'C:\Windows\Microsoft.Net\assembly\GAC_32\System.EnterpriseServices\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.EnterpriseServices.dll', Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
'MyApp.vshost.exe' (Managed (v4.0.30319)): Loaded 'C:\Windows\Microsoft.Net\assembly\GAC_32\System.EnterpriseServices\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.EnterpriseServices.Wrapper.dll', Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
'MyApp.vshost.exe' (Managed (v4.0.30319)): Loaded 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Runtime.Serialization\v4.0_4.0.0.0__b77a5c561934e089\System.Runtime.Serialization.dll', Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
'MyApp.vshost.exe' (Managed (v4.0.30319)): Loaded 'EntityFrameworkDynamicProxies-MyApp.Model.Domain.People'
'MyApp.vshost.exe' (Managed (v4.0.30319)): Loaded 'EntityFrameworkDynamicProxies-MyApp.Model.Domain.Security'
53:39:965 Out of GetUserAsync
53:39:968 out of AuthenticateAsync
The thread '<No Name>' (0x1e98) has exited with code 0 (0x0).
The thread '<No Name>' (0x17d4) has exited with code 0 (0x0).
The thread '<No Name>' (0x175c) has exited with code 0 (0x0).
The thread '<No Name>' (0x220) has exited with code 0 (0x0).
The thread '<No Name>' (0x1dc8) has exited with code 0 (0x0).
The thread '<No Name>' (0x1af8) has exited with code 0 (0x0).
4

1 に答える 1

4

'async'とマークされたメソッドは、最初の'await'が発生する時点まで同期しています。そのため、最初のコードで発生する処理に時間がかかりすぎる場合(200ミリ秒以上がWinRTのガイドラインであると思いますが、これは妥当と思われます)、awaitを早めに挿入して、コードをより速く戻すことができます。

たとえば、LoginButton_Clickで、「await Task.Yield()」の最初の行を挿入できます。これにより、呼び出しをUIスレッドにすばやく戻すことができます。

現在、その変更だけでも、async / awaitの動作により、メソッドはすべてUIスレッドで実行されます。多くの場合、それはユーザーが実際に起こっていることを期待しているので('async'修飾子はその点でちょっと混乱しています)、それはハンドラーの開始時に混乱することなくできることなので、私はまだ最初にその変更を行うのが好きですスタックのさらに下にあるもので。

上記が不十分な場合に実行できる次のステップ(コンテキストの初期化に時間がかかりすぎ、UIスレッドで発生し、UIがフリーズするなど、わずかに異なる時点で)は、そうでない部分を取得します。 UIスレッドで発生する必要があり、UIスレッドだけでなく、任意のスレッドで処理できることを通知する必要があります。これは、コードが現在「十分に高速」で実行されて目立った問題にならないシナリオであっても、応答性のためにとにかく一般的に良い習慣です。

そのために、タスクに追加ConfigureAwait(false)を使用します。

  • GetUserAsyncメソッドは、FirstOrDefaultAsync呼び出しの後に追加(「チェーン」)する必要があります
    • あるいは、IMHOが少しすっきりしているのは、GetUserAsyncメソッドのasync / awaitキーワードを削除し、FirstOrDefaultAsyncから取得したタスクを返すことです。async / awaitは、このメソッドで何もそのまま「購入」するわけではありません、IMHO :)
  • Authenticateでは、GetUserAsync呼び出しの後に追加する必要があります
    • ここで私がよくわからない潜在的な「落とし穴」の1つは、CurrentUserがUIにデータバインドされているかどうかです。_serviceのメンバーであるため、そうではないと思いますが、そうである場合でも、WPFは、UI以外のスレッドで更新されるデータバインドされたアイテムで問題なく、変更をUIにマーシャリングすることを処理すると思います。 (ディスパッチャー?)スレッド。これは、UIにデータバインドされている非UIのプロパティを更新すると、ターゲットコントロールを手動で更新した場合と同じクロススレッドエラーが発生するSilverlightのようなフレームワークとは異なります。私がこれについて間違っている場合、および1)CurrentUserはUIにデータバインドされており、2)非UIスレッドでのデータバインドされた更新によりランタイム例外が発生する場合は、このメソッドでのConfigureAwait(false)の追加を回避してください。これの長さについて申し訳ありませんが、この特定の変更について少し確信が持てないことを関連付けようとしています。:)
  • LoginButton_Clickでは、メソッドの残りの部分(this.Close)がUIスレッドで発生する必要があるため、追加しないでください。ここでのConfigureAwait(false)はそれを壊します

これらの変更が両方とも行われると、1)できるだけ早く制御を呼び出し元に戻す(イベントハンドラーに同期するコードの量を最小限に抑える)ことと、2)必要のない作業を行うことの両方が行われます。他のスレッドのUIスレッドにあること。これは、UIが「フリーズ」しないことを意味するはずです。

これらの変更後もフリーズする場合は、デバッガーで実行する必要があります。フリーズしたら、UIスレッドのスタックが問題のあるコードを見つけるために中断します。:)

幸運を!

于 2012-11-07T13:37:19.393 に答える