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

时间:2012-04-26 13:59:53

标签: wpf multithreading task-parallel-library dispatcher sta

我知道有关SO的这个主题有一些答案,但我无法让任何解决方案适合我。我试图从一个从datatemplate中发出的ICommand打开一个新窗口。当实例化新窗口时(

使用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();
    }
}

由于

EDIT。根据到目前为止的响应,我想指出我还在当前调度程序上尝试了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;

我很茫然。

3 个答案:

答案 0 :(得分:7)

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

E.g。如果您有对主应用程序窗口(MainWindow)的引用,则可以执行

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

更新: 小心无法盲目地呼叫Dispatcher.CurrentDispatcher,因为它没有按照您的想法执行。 documentation表示CurrentDispatcher

  

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

这就是必须使用与已存在的UI控件相关联的Dispatcher的原因(就像上面示例中的主窗口一样)。

答案 1 :(得分:5)

使用TPL,您可以使用StaTaskScheduler from the TPL Extras

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

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

答案 2 :(得分:3)

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

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

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

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

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