6

我知道关于这个主题有一些关于 SO 的答案,但我无法获得任何适合我的解决方案。我正在尝试从数据模板中触发的 ICommand 打开一个新窗口。当实例化新窗口时(在“new MessageWindowP”内),以下两个都给出了上述错误:

使用 TPL/FromCurrentSynchronizationContext 更新:有效

public class ChatUserCommand : ICommand
{
    public void Execute(object sender)
    {
        if (sender is UserC)
        {
            var user = (UserC)sender;
            var scheduler = TaskScheduler.FromCurrentSynchronizationContext();                  
            Task.Factory.StartNew(new Action<object>(CreateMessageWindow), user,CancellationToken.None, TaskCreationOptions.None,scheduler);         
        }
    }

    private void CreateMessageWindow(object o)
    {
        var user = (UserC)o;
        var messageP = new MessageWindowP();
        messageP.ViewModel.Participants.Add(user);
        messageP.View.Show();
    }
}

使用 ThreadStart: 更新:不推荐,请参阅 Jon 的回答

public class ChatUserCommand : ICommand
{
    public void Execute(object sender)
    {
        if (sender is UserC)
        {
            var user = (UserC)sender;

            var t = new ParameterizedThreadStart(CreateMessageWindow);
            var thread = new Thread(t);
            thread.SetApartmentState(ApartmentState.STA);
            thread.Start(sender);           
        }
    }

    private void CreateMessageWindow(object o)
    {
        var user = (UserC)o;
        var messageP = new MessageWindowP();
        messageP.ViewModel.Participants.Add(user);
        messageP.View.Show();
    }
}

谢谢

编辑。根据到目前为止的响应,我想指出我还在当前调度程序上尝试了 BeginInvoke,以及在原始方法中执行代码(这就是代码开始的方式)。见下文:

BeginInvoke 更新:不推荐查看 Jon 的回答

public class ChatUserCommand : ICommand
{
    public void Execute(object sender)
    {
        if (sender is UserC)
        {
            var user = (UserC)sender;
            Dispatcher.CurrentDispatcher.BeginInvoke(new Action<object>(CreateMessageWindow), sender);       
        }
    }

    private void CreateMessageWindow(object o)
    {
        var user = (UserC)o;
        var messageP = new MessageWindowP();
        messageP.ViewModel.Participants.Add(user);
        messageP.View.Show();
    }
}

在同一个线程 更新:如果你已经在 UI 线程上工作

public class ChatUserCommand : ICommand
{
    public void Execute(object sender)
    {
        if (sender is UserC)
        {
            var user = (UserC)sender;
            var messageP = new MessageWindowP();
            messageP.ViewModel.Participants.Add(user);
            messageP.View.Show();    
        }
    }

}

BeginInvoke,使用对第一个/主窗口 更新的调度程序的引用:有效

 public void Execute(object sender)
   {
       if (sender is UserC)
       {
            var user = (UserC)sender;
                    GeneralManager.MainDispatcher.BeginInvoke(
                               DispatcherPriority.Normal,
                               new Action(() => this.CreateMessageWindow(user)));      
        }
    }

其中 GeneralManager.MainDispatcher 是对我创建的第一个窗口的 Dispatcher 的引用:

     [somewhere far far away]
        mainP = new MainP();
        MainDispatcher = mainP.View.Dispatcher;

我不知所措。

4

3 回答 3

7

调用线程不仅必须是 STA,还必须有消息循环。您的应用程序中只有一个线程已经具有消息循环,这就是您的主线程。所以你应该使用Dispatcher.BeginInvoke从你的主线程打开你的窗口。

例如,如果您有对主应用程序窗口 ( MainWindow) 的引用,您可以这样做

MainWindow.BeginInvoke(
    DispatcherPriority.Normal, 
    new Action(() => this.CreateMessageWindow(user)));

更新: 小心:不能盲目地打电话Dispatcher.CurrentDispatcher,因为它没有做你认为它做的事情。文档CurrentDispatcher:_

获取当前正在执行的线程的 Dispatcher,如果尚未与线程关联,则创建一个新的 Dispatcher。

这就是为什么您必须使用Dispatcher与已经存在的 UI 控件关联的原因(如上例中的主窗口)。

于 2012-04-26T14:03:42.553 回答
5

使用 TPL,您可以使用TPL Extras 中的 StaTaskScheduler

它将在 STA 线程上运行任务。

仅用于 COM。从未尝试运行多个 UI 线程。

于 2012-04-26T15:00:29.963 回答
3

您正在尝试从后台线程创建一个窗口。由于各种原因,您无法做到这一点。通常,您需要在主应用程序线程中创建窗口。

对于您的情况,一个简单的想法是立即执行(只需调用CreateMessageWindowinside Execute)而不是分配 a Task,因为您的命令肯定会从主(UI)线程触发。如果您不确定Execute运行的线程,可以使用Dispatcher.BeginInvoke().

您希望新窗口在非主线程中运行的情况确实很少。如果您的情况确实需要这样做,您应该在Dispatcher.Run();之后添加messageP.View.Show();(使用代码的第二个变体)。这将在新线程中启动消息循环。

您不应该尝试在 TPL 的线程中运行 window,因为这些线程通常是线程池线程,因此不受您的控制。例如,您不能确保它们是 STA(通常是 MTA)。

编辑:
从更新代码中的错误来看,很明显是Execute在一些非 UI 线程中运行。尝试使用Application.Current.Dispatcher而不是Dispatcher.CurrentDispatcher. (CurrentDispatcher表示当前线程的调度器,如果当前线程不是主线程可能会出错。)

于 2012-04-26T14:04:48.847 回答