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

问题描述 投票:0回答:3

我知道关于这个主题有一些答案,但我无法得到任何适合我的解决方案。我正在尝试从数据模板内触发的 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更新:不建议参见乔恩的回答

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 是对我创建的第一个窗口的调度程序的引用:

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

我很茫然。

wpf multithreading task-parallel-library dispatcher sta
3个回答
8
投票

调用线程不能是STA,但它还必须具有消息循环。您的应用程序中只有一个线程已经具有消息循环,这就是您的主线程。因此,您应该使用

Dispatcher.BeginInvoke
从主线程打开窗口。

例如如果您有主应用程序窗口的参考(

MainWindow
),您可以执行以下操作

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

更新:小心:不能盲目地调用

Dispatcher.CurrentDispatcher
,因为它不会做你认为它会做的事情。 文档
CurrentDispatcher

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

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

Dispatcher


5
投票

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

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

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


4
投票

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

对于您的情况,一个简单的想法是立即执行(只需在

CreateMessageWindow
内调用
Execute
)而不是分配
Task
,因为您的命令肯定会从主(UI)线程触发。如果您不确定
Execute
运行的线程,您可以使用
Dispatcher.BeginInvoke()
将其编组到 UI 线程。

在极少数情况下,您希望新窗口在非主线程中运行。如果您的情况确实需要这样做,您应该在

Dispatcher.Run();
之后添加
messageP.View.Show();
(使用代码的第二种变体)。这将在新线程中启动消息循环。

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

编辑:
从更新代码中的错误来看,很明显

Execute
在某些非 UI 线程中运行。尝试使用
Application.Current.Dispatcher
而不是
Dispatcher.CurrentDispatcher
。 (
CurrentDispatcher
表示当前线程的调度程序,如果当前线程不是主线程,则可能会出错。)

© www.soinside.com 2019 - 2024. All rights reserved.