问题
I am developing an application that should receive commands through a socket interface and then execute them in a GUI. This application is being developed in C# .NET 4.0 and it uses WPF for its GUI.
The socket interface has a worker thread that keeps listening to the socket and processing its commands, so if for example a Show Popup command is received, the worker thread calls a manager class that is responsible for creating the popup and showing it on the main screen.
The manager method that creates the popup and then calls the main screen is the following:
public void ProcessPopup(PopupModel model)
{
switch (model.ScreenType)
{
case Screens.Type1:
popup = new PopupType1();
break;
case Screens.Type2:
popup = new PopupType2();
break;
case Screens.Type3:
popup = new PopupType3();
break;
case Screens.Type4:
popup = new PopupType4();
break;
}
viewModel.SetModel(model);
if (!Dispatcher.CurrentDispatcher.Equals(App.Current.Dispatcher))
{
App.Current.Dispatcher.Invoke((ThreadStart)delegate { mainScreen.ShowPopup(popup); });
}
else
{
mainScreen.ShowPopup(popup);
}
}
The class PopupType1 is:
public partial class PopupType1 : UserControl
{
public PopupType1 ()
{
InitializeComponent();
}
}
The problem is that when I craete a new PopupType1 object I get the following exception:
System.InvalidOperationException: The calling thread must be STA, because many UI components require this.
at System.Windows.Input.InputManager..ctor()
at System.Windows.Input.InputManager.GetCurrentInputManagerImpl()
at System.Windows.Input.InputManager.get_Current()
at System.Windows.Input.KeyboardNavigation..ctor()
at System.Windows.FrameworkElement.FrameworkServices..ctor()
at System.Windows.FrameworkElement.EnsureFrameworkServices()
at System.Windows.FrameworkElement..ctor()
at System.Windows.Controls.Control..ctor()
at System.Windows.Controls.UserControl..ctor()
at MyApp.Views.PopupType1..ctor()
at MyApp.Manager.ProcessPopup(PopupModel model)
at MyApp.CommunicationController.ProcessAsync(XDocument messageXml)
I've tried several things already like transforming my worker thread into a STA thread, or creating a new STA thread just to handle the creation of the Popup, but they caused more problems than they solved.
Finally it is important to mention that I am doing this, because my application is experiencing several "freezes" during its operation and I believe that they are related to the fact that the WPF GUI thread is too overwhelmed with tasks to be properly responsive, therefore I am trying to separate the non-GUI processing from the GUI thread.
回答1:
You need to create the UI control on the UI thread as well. So basically all of ProcessPopup needs to be executed on the UI thread in your case and not just mainScreen.ShowPopup()
回答2:
You're creating the popup on the background thread.
That won't work; you can only create control on the UI thread.
You should separate the expensive (slow) logic from the UI classes and do that alone on the background thread.
回答3:
If PopupType contains any controls (and it looks like it does), it should be created on the main GUI thread.
回答4:
By design only the thread that created the UI object (main) can access the UI object. From your description "the socket interface has a worker thread" so that interface does not have access to the UI. The callback from the backgroundworker is where you gain access to the UI. Since you want to keep the listener hot I think "progress" may work better for access to the UI.
回答5:
You should create the UI element in the UI thread. You could do the following for example:
private static UserControl CreatePopup(PopupModel model){
switch (model.ScreenType)
{
case Screens.Type1:
popup = new PopupType1();
break;
case Screens.Type2:
popup = new PopupType2();
break;
case Screens.Type3:
popup = new PopupType3();
break;
case Screens.Type4:
popup = new PopupType4();
break;
}
}
And change the ProcessPopup to:
public void ProcessPopup(PopupModel model)
{
viewModel.SetModel(model);
App.Current.Dispatcher.BeginInvoke(
new Action(()=>{
mainScreen.ShowPopup(CreatePopup(model));
}
));
}
Also, notice the use of BeginInvoke instead of Invoke. This is to ensure your UI updates asynchronously so that you don't have to block your worker thread on some UI stuff.
来源:https://stackoverflow.com/questions/9639284/c-sharp-gui-thread-error