0

PRISMアーキテクチャにWPFアプリケーションがあります。

アプリの読み込み時に「メインリージョン」に表示される「ログインビュー」があります。

ユーザーが[ログイン]を押すと、WCFサービスに接続し、ユーザーを認証して、サービスからそのユーザーの役割のリストを取得します。

次に、ユーザーの役割に応じて、「モジュールマネージャー」を使用してさまざまなモジュールをロードします。

問題は-「ログイン」ボタンが押された後のすべての作業を別のスレッドで実行したいのです。サービスへの接続などに時間がかかる可能性があり、UIがフリーズしたくないからです。

しかし、コードを「接続、認証、役割の取得、モジュールのロード」に別のスレッドで配置すると、「_ moduleManager.LoadModule」を呼び出すと、次のような例外が発生します。

多くのUIコンポーネントがこれを必要とするため、呼び出しスレッドはSTAである必要があります。

どうすればこれを解決できますか?

私はさまざまな解決策を試しました。新しいスレッドの「ApartmentState=STA」を設定しようとしましたが、役に立ちませんでした。View-Modelのコンストラクターに'Dispatcher'オブジェクトを保存し、'LoadModule'を呼び出すときに'dispatcher.Invoke'を実行することを考えましたが、それは悪い設計です(View-ModelはDispatcherを使用しないでください。テストには適していません)。

どうすればこれを解決できるのでしょうか?

'LoadModule'だけが私に悲しみを与え、他のすべてのものはうまく機能します。

[更新]-コードサンプルを追加:

[Export]
public class LoginViewModel : NotificationObject
{
    [ImportingConstructor]
    public LoginViewModel(IRegionManager regionManager, IModuleManager moduleManager)
    {
        this.LoginCommand   = new DelegateCommand(LoginExecute, LoginCanExecute);
        this._regionManager = regionManager;
        this._moduleManager = moduleManager;
    }

    private void LoginExecute()
    {
        IsBusy = true; // Set this to 'true' so controls go disabled
        LoginStatus = ""; // Clear the 'login status' string


        Thread loginThread = new Thread(new ThreadStart(LoginWork));
        loginThread.SetApartmentState(ApartmentState.STA);
        loginThread.Start();
    }

    private void LoginWork()
    {
        ParamsToGetRoles param = new ParamsToGetRoles
        {
            Username = Username,
            InputtedPassword = Password
        };

        try
        {
            // Connect to the secure service, and request the user's roles
            _clientSecure = new AuthenticationServiceClient("WSHttpBinding_MyService");
            _clientSecure.ClientCredentials.UserName.UserName = param.Username;
            _clientSecure.ClientCredentials.UserName.Password = param.InputtedPassword;
            _clientSecure.ChannelFactory.Faulted += new EventHandler(ChannelFactory_Faulted);
            var local = _clientSecure.ChannelFactory.CreateChannel();
            _clientSecure.GetRolesCompleted += new EventHandler<GetRolesCompletedEventArgs>(clientSecure_GetRolesCompleted);
            _clientSecure.GetRolesAsync(param);
        }
        catch (Exception ex)
        {
        Console.WriteLine("Exception : " + ex.Message.ToString());
        }
    }

    void clientSecure_GetRolesCompleted(object sender, GetRolesCompletedEventArgs e)
    {
        if (e.Error == null)
        {
            _clientSecure.Close();
            LoginSuccess(e.Result.UserRoles);
        }
        else
        {
            LoginFailure("Unable to authenticate");
        }
        _clientSecure = null;
    }

    private void LoginSuccess(List<UserTypeEnum> rolesOfAuthenticatedUser)
    {
        LoginStatus = "Success";

        if (rolesOfAuthenticatedUser.Contains(UserTypeEnum.Administrator))
        {
            // This is what throws the exception !
            // This is called by the 'EndInvoke' of the 'GetRoles' operation, 
            // Which was called in the 'LoginWork' function which was run on a separate thread !
            _moduleManager.LoadModule(WellKnownModuleNames.ModuleAdmin);
        }

        NavigateToMainMenu();

        this.IsBusy = false;
    }
}
4

1 に答える 1

2

デバッガーをアタッチし、ブレークポイントを に設定してスレッド ウィンドウを調べる必要がありますclientSecure_GetRolesCompleted。loginThread から呼び出されていないことは確かですLoginWork。loginThread で実行されている間、非同期操作の完了イベントにイベントハンドラーが追加されます。Async = さらに別のスレッドで実行されます。

したがって、おそらく何が起こるか:

  • LoginExecuteUIスレッドで実行
  • 別のスレッド B を開始して実行するLoginWork
  • GetRolesAsyncそのため、ロールを取得するためにスレッド C ( STA ではない) を開始します。
  • スレッド C は最終的にスレッド B ではなく「clientSecure_GetRolesCompleted」を呼び出します

LoginWorkしたがって、実際の作業は非同期操作として既に行われているため、別のスレッドは必要ありません。読み込みの問題を回避するには、「ロールの取得」スレッドを STA にするか、ディスパッチャーを使用LoginSuccessして UI スレッドで呼び出されるようにします。

于 2012-07-24T08:40:28.003 に答える