【问题标题】:"The calling thread must be STA" workaround“调用线程必须是 STA”解决方法
【发布时间】:2012-04-26 13:59:53
【问题描述】:

我知道关于这个主题有一些关于 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;

我很茫然。

【问题讨论】:

  • 在同一个线程和BeginInvoke 中调用有什么问题?您的Execute 在哪个线程中运行?
  • 你不能像这里一样使用Dispatcher.CurrentDispatcher。查看我的答案的更新。
  • 伙计们。仍然没有运气... :( 再次查看更新。
  • 您能否发布简短但完整的代码,我们可以用来重现您的问题?
  • OK svick。我将尝试从代码库中提取代码并创建一个最小的可重现项目。

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


【解决方案1】:

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

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

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

更新: 小心:不能盲目地打电话给Dispatcher.CurrentDispatcher,因为它不会做你认为它做的事。 documentation 表示CurrentDispatcher

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

这就是为什么您必须使用与现有 UI 控件关联的Dispatcher(如上例中的主窗口)。

【讨论】:

  • 乔恩还有其他想法吗?我尝试使用主窗口的调度程序。
【解决方案2】:

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

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

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

【讨论】:

    【解决方案3】:

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

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

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

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

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

    【讨论】:

    • 您好。谢谢你的帮助。你说的一切都是有道理的,但我只使用了这些技术,因为在 Execute 中运行代码会导致同样的错误。有关我尝试过的更多内容,请参阅更新后的问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多