问题
I know there are a few answers on this topic on SO, but I can not get any of the solutions working for me. I am trying to open a new window, from an ICommand fired from within a datatemplate. Both of the following give the aforementioned error when the new window is instantiated (within "new MessageWindowP"):
Using TPL/FromCurrentSynchronizationContext Update: works
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();
}
}
Using ThreadStart: Update: not recommended, see Jon's answer
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();
}
}
Thanks
EDIT. Based on the responses so far, I'd like to point out that I have also tried BeginInvoke on the current dispatcher, as well as executing the code in the original method (that's how the code started). See below:
BeginInvoke Update: not recommended see Jon's answer
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();
}
}
In same thread Update: works if you are on UI thread already
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, using reference to dispatcher of first/main window Update: works
public void Execute(object sender)
{
if (sender is UserC)
{
var user = (UserC)sender;
GeneralManager.MainDispatcher.BeginInvoke(
DispatcherPriority.Normal,
new Action(() => this.CreateMessageWindow(user)));
}
}
where GeneralManager.MainDispatcher is a reference to the Dispatcher of the first window I create:
[somewhere far far away]
mainP = new MainP();
MainDispatcher = mainP.View.Dispatcher;
I'm at a loss.
回答1:
The calling thread must not only be STA, but it must also have a message loop. There's only one thread in your application that already has a message loop, and that's your main thread. So you should use Dispatcher.BeginInvoke
to open your window from your main thread.
E.g. if you have a reference to your main application window (MainWindow
), you can do
MainWindow.BeginInvoke(
DispatcherPriority.Normal,
new Action(() => this.CreateMessageWindow(user)));
Update: Be careful: you cannot blindly call Dispatcher.CurrentDispatcher
because it doesn't do what you think it does. The documentation says that CurrentDispatcher
:
Gets the Dispatcher for the thread currently executing and creates a new Dispatcher if one is not already associated with the thread.
That's why you must use the Dispatcher
associated with an already-existing UI control (like your main window as in the example above).
回答2:
With TPL you can use the StaTaskScheduler from the TPL Extras
It will run tasks on STA threads.
Only used it for COM. Never tried to run several UI threads.
回答3:
You are trying to create a window from a background thread. You cannot do it due to a variety of reasons. Typically you need to create the window in the main application thread.
For your case, a simple idea would be just do it immediately (just call CreateMessageWindow
inside Execute
) instead of allocating a Task
, because your command will definitely fire from the main (UI) thread. If you are unsure about the thread where your Execute
runs, you can marshal it to the UI thread using Dispatcher.BeginInvoke()
.
There are really few cases when you would want your new window to run in a non-main thread. If this is really really needed in your case, you should add Dispatcher.Run();
after messageP.View.Show();
(using the second variant of the code). This starts the message loop in the new thread.
You shouldn't try to run window in TPL's thread, because these threads are typically threadpool threads, and therefore out of your control. For example, you cannot ensure they are STA (typically, they are MTA).
Edit:
from the error in your updated code, it's clear that the Execute
runs in some non-UI thread. Try using Application.Current.Dispatcher
instead of Dispatcher.CurrentDispatcher
. (CurrentDispatcher
means the dispatcher of the current thread, which may be wrong if the current thread is not the main one.)
来源:https://stackoverflow.com/questions/10335031/the-calling-thread-must-be-sta-workaround