0

我在 PRISM 架构中有一个 WPF 应用程序。

当应用程序加载时,我有一个显示在“主要区域”中的“登录视图”。

当用户按下“登录”时 - 我连接到 WCF 服务,对用户进行身份验证,并从服务中获取该用户的角色列表。

然后 - 根据用户的角色 - 我使用“模块管理器”加载不同的模块。

问题是 - 我希望按下“登录”按钮后的所有工作都在单独的线程中完成,因为连接到服务等可能需要时间,而且我不希望 UI 被冻结。

但是 - 如果我将代码“连接、验证、获取角色、加载模块”放在一个单独的线程中 - 当我调用“_moduleManager.LoadModule”时会出现异常:

调用线程必须是 STA,因为许多 UI 组件都需要这个。

我该如何解决这个问题?

我尝试了不同的解决方案。我试图设置新线程的“Apartment State = 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 = 在另一个线程中运行。

那么可能会发生什么:

  • LoginExecute在 UI 线程中执行
  • 启动一个单独的线程 B 运行LoginWork
  • 调用GetRolesAsync所以启动一个线程 C(不是STA)来获取角色
  • 线程 C 最终调用“clientSecure_GetRolesCompleted”,而不是线程 B

因此,您不需要单独的线程,LoginWork因为实际工作已经作为异步操作完成。要解决加载问题,请尝试使“获取角色”线程成为 STA,或者更好的是,使用调度程序以便LoginSuccess在 UI 线程上调用。

于 2012-07-24T08:40:28.003 回答