Обходной путь «вызывающий поток должен быть STA»

Я знаю, что есть несколько ответов на эту тему по SO, но я не могу получить ни одно из решений, работающих на меня. Я пытаюсь открыть новое окно из ICommand, запущенного из таблицы данных. Оба приведенных ниже дают вышеупомянутую ошибкуwhen the new window is instantiated (внутри & quot; нового MessageWindowP & quot;):

Using TPL/FromCurrentSynchronizationContext Update: works

<code>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();
    }
}
</code>

Using ThreadStart: Update: not recommended, see Jon's answer

<code>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();
    }
}
</code>

Спасибо

РЕДАКТИРОВАТЬ. Основываясь на полученных ответах, я хотел бы отметить, что я также пробовал BeginInvoke на текущем диспетчере, а также выполняю код в исходном методе (то есть, как код начинался). Увидеть ниже:

BeginInvoke Update: not recommended see Jon's answer

<code>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();
    }
}
</code>

In same thread Update: works if you are on UI thread already

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

}
</code>

BeginInvoke, using reference to dispatcher of first/main window Update: works

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

где GeneralManager.MainDispatcher - это ссылка на Dispatcher первого окна, которое я создаю:

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

Я в растерянности.

 Harry Mexican27 апр. 2012 г., 11:13
ОК, свик. Я постараюсь вырвать код из кодовой базы и создать минимально воспроизводимый проект.
 svick26 апр. 2012 г., 20:24
Не могли бы вы опубликовать короткий, но полный код, который мы могли бы использовать для воспроизведения вашей проблемы?
 Vlad26 апр. 2012 г., 16:23
И в чем была проблема с вызовом в той же теме иBeginInvoke? В какой теме вашExecute бежать?
 Jon26 апр. 2012 г., 16:37
Вы не можете использоватьDispatcher.CurrentDispatcher как ты здесь. Смотрите обновление к моему ответу.
 Harry Mexican26 апр. 2012 г., 16:59
Ребята. Все еще не повезло ... :( см. Обновление снова.

Ответы на вопрос(3)

StaTaskScheduler от TPL Extras

Он будет запускать задачи в потоках STA.

Используется только для COM. Никогда не пытался запустить несколько потоков пользовательского интерфейса.

Решение Вопроса

only быть STA, но он также должен иметь цикл сообщений. В вашем приложении только один поток, в котором уже есть цикл обработки сообщений, и который является вашим основным потоком. Так что вы должны использоватьDispatcher.BeginInvoke чтобы открыть ваше окно из основного потока.

Например. если у вас есть ссылка на главное окно приложения (MainWindow), ты можешь сделать

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

Update: Be careful: выcannot слепой вызовDispatcher.CurrentDispatcher потому что он не делает то, что вы думаете, что он делает.документация Говорит, чтоCurrentDispatcher:

Gets the Dispatcher for the thread currently executing and creates a new Dispatcher if one is not already associated with the thread.

Вот почему выmust использоватьDispatcher связан с уже существующим элементом управления пользовательским интерфейсом (как ваше главное окно, как в примере выше).

 Harry Mexican26 апр. 2012 г., 17:52
Есть другие идеи, Джон? Я пытался использовать Диспетчер моего главного окна.

по разным причинам. Обычно вам нужно создать окно в главном потоке приложения.

Для вашего случая простая идея будет просто сделать это немедленно (просто позвонитеCreateMessageWindow внутриExecute) вместо выделенияTaskпотому что ваша команда определенно будет запускаться из основного потока (UI). Если вы не уверены в теме, где вашExecute работает, вы можете направить его в поток пользовательского интерфейса, используяDispatcher.BeginInvoke().

На самом деле очень мало случаев, когда вы хотите, чтобы ваше новое окно запускалось в неосновном потоке. Если это действительно нужно в вашем случае, вы должны добавитьDispatcher.Run(); послеmessageP.View.Show(); (используя второй вариант кода). Это запускает цикл сообщений в новой теме.

Вы не должны пытаться запустить окно в потоке TPL, потому что эти потоки, как правило, являются потоками пула потоков и, следовательно, находятся вне вашего контроля. Например, вы не можете гарантировать, что они STA (обычно это MTA).

Редактировать:
из ошибки в вашем обновленном коде ясно, чтоExecute работает в каком-то потоке без пользовательского интерфейса. Попробуйте использоватьApplication.Current.Dispatcher вместоDispatcher.CurrentDispatcher. (CurrentDispatcher означает диспетчер текущего потока, что может быть неправильно, если текущий поток не является основным.)

 26 апр. 2012 г., 16:27
@Harry: также обновил ответ.
 Harry Mexican26 апр. 2012 г., 16:24
Всем привет. Спасибо за вашу помощь. Все, что вы говорите, имеет смысл, но я прибегнул только к этим методам, потому что запуск кода внутри Execute вызвал ту же ошибку. Смотрите обновленный вопрос, чтобы узнать больше вещей, которые я пробовал.

Ваш ответ на вопрос