Вопрос по task-parallel-library, wpf, multithreading, sta, dispatcher – Обходной путь «вызывающий поток должен быть STA»

6

Я знаю, что есть несколько ответов на эту тему по 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 Mexican
Не могли бы вы опубликовать короткий, но полный код, который мы могли бы использовать для воспроизведения вашей проблемы? svick
И в чем была проблема с вызовом в той же теме иBeginInvoke? В какой теме вашExecute бежать? Vlad
Вы не можете использоватьDispatcher.CurrentDispatcher как ты здесь. Смотрите обновление к моему ответу. Jon
Ребята. Все еще не повезло ... :( см. Обновление снова. Harry Mexican

Ваш Ответ

3   ответа
4

StaTaskScheduler от TPL Extras

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

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

7

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 Mexican
3

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

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

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

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

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

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

Похожие вопросы