"调用线程必须是STA"解决方法

Har*_*can 6 wpf multithreading dispatcher sta task-parallel-library

我知道在这个主题上有一些关于SO的答案,但是我无法让任何解决方案适合我.我试图从一个从datatemplate中发出的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();
    }
}
Run Code Online (Sandbox Code Playgroud)

使用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();
    }
}
Run Code Online (Sandbox Code Playgroud)

谢谢

编辑.根据到目前为止的响应,我想指出我还在当前调度程序上尝试了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();
    }
}
Run Code Online (Sandbox Code Playgroud)

在同一个线程中 更新:如果您已经在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();    
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

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

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

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

     [somewhere far far away]
        mainP = new MainP();
        MainDispatcher = mainP.View.Dispatcher;
Run Code Online (Sandbox Code Playgroud)

我不知所措.

Jon*_*Jon 7

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

例如,如果您有对主应用程序窗口(MainWindow)的引用,则可以执行此操作

MainWindow.BeginInvoke(
    DispatcherPriority.Normal, 
    new Action(() => this.CreateMessageWindow(user)));
Run Code Online (Sandbox Code Playgroud)

更新: 小心:不能盲目打电话,Dispatcher.CurrentDispatcher因为它没有按照你的想法做到.该文件CurrentDispatcher:

获取当前正在执行的线程的Dispatcher,并创建一个新的Dispatcher(如果尚未与该线程关联).

这就是为什么你必须使用Dispatcher与已经存在的UI控件相关联的原因(就像上面的例子中的主窗口一样).


Vla*_*lad 5

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

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

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

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

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


adr*_*anm 5

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

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

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