我目前正在编写一个组件,其中一些部分应该在UI线程上运行(解释会很长)。 最简单的方法是将控件传递给它,并在其上使用InvokeRequired/Invoke。 但我认为将控件引用传递给“数据/后台”组件不是一个好的设计,因此我正在寻找一种在无需控件的情况下在UI线程上运行代码的方法。 类似WPF中的Application.Dispatcher.Invoke的东西...
有什么想法, 谢谢 马丁
我目前正在编写一个组件,其中一些部分应该在UI线程上运行(解释会很长)。 最简单的方法是将控件传递给它,并在其上使用InvokeRequired/Invoke。 但我认为将控件引用传递给“数据/后台”组件不是一个好的设计,因此我正在寻找一种在无需控件的情况下在UI线程上运行代码的方法。 类似WPF中的Application.Dispatcher.Invoke的东西...
有什么想法, 谢谢 马丁
有一种更好的、更抽象的方法可以在WinForms和WPF上实现这个功能:
System.Threading.SynchronizationContext.Current.Post(theMethod, state);
这能够工作是因为WindowsForms安装了一个WindowsFormsSynchronizationContext
对象作为当前的同步上下文。WPF也采取类似的做法,安装它自己专用的同步上下文(DispatcherSynchronizationContext
)。
.Post
对应于control.BeginInvoke
,.Send
对应于control.Invoke
。
首先,在您的表单构造函数中,保留对SynchronizationContext.Current
对象的类级引用(实际上是一个WindowsFormsSynchronizationContext
)。
public partial class MyForm : Form {
private SynchronizationContext syncContext;
public MyForm() {
this.syncContext = SynchronizationContext.Current;
}
}
然后,在你的类中的任何地方,使用这个上下文来向UI发送消息:
public partial class MyForm : Form {
public void DoStuff() {
ThreadPool.QueueUserWorkItem(_ => {
// worker thread starts
// invoke UI from here
this.syncContext.Send(() =>
this.myButton.Text = "Updated from worker thread");
// continue background work
this.syncContext.Send(() => {
this.myText1.Text = "Updated from worker thread";
this.myText2.Text = "Updated from worker thread";
});
// continue background work
});
}
}
public class MyBackgroundThread : BackgroundWorker
{
public event EventHandler<ClassToPassToUI> IWantTheUIToDoSomething;
public MyStatus TheUIWantsToKnowThis { get { whatever... } }
public void TheUIWantsMeToDoSomething()
{
// Do something...
}
protected override void OnDoWork(DoWorkEventArgs e)
{
// This is called when the thread is started
while (!CancellationPending)
{
// The UI will set IWantTheUIToDoSomething when it is ready to do things.
if ((IWantTheUIToDoSomething != null) && IHaveUIData())
IWantTheUIToDoSomething( this, new ClassToPassToUI(uiData) );
}
}
}
public partial class MyUIClass : Form
{
MyBackgroundThread backgroundThread;
delegate void ChangeUICallback(object sender, ClassToPassToUI uiData);
...
public MyUIClass
{
backgroundThread = new MyBackgroundThread();
// Do this when you're ready for requests from background threads:
backgroundThread.IWantTheUIToDoSomething += new EventHandler<ClassToPassToUI>(SomeoneWantsToChangeTheUI);
// This will run MyBackgroundThread.OnDoWork in a background thread:
backgroundThread.RunWorkerAsync();
}
private void UserClickedAButtonOrSomething(object sender, EventArgs e)
{
// Really this should be done in the background thread,
// it is here as an example of calling a background task from the UI.
if (backgroundThread.TheUIWantsToKnowThis == MyStatus.ThreadIsInAStateToHandleUserRequests)
backgroundThread.TheUIWantsMeToDoSomething();
// The UI can change the UI as well, this will not need marshalling.
SomeoneWantsToChangeTheUI( this, new ClassToPassToUI(localData) );
}
void SomeoneWantsToChangeTheUI(object sender, ClassToPassToUI uiData)
{
if (InvokeRequired)
{
// A background thread wants to change the UI.
if (iAmInAStateWhereTheUICanBeChanged)
{
var callback = new ChangeUICallback(SomeoneWantsToChangeTheUI);
Invoke(callback, new object[] { sender, uiData });
}
}
else
{
// This is on the UI thread, either because it was called from the UI or was marshalled.
ChangeTheUI(uiData)
}
}
}
传递一个 System.ComponentModel.ISynchronizeInvoke 怎么样?这样你就可以避免传递一个 Control。
params object p
,可以将其强类型化以适应自己的目的。这只是一个简单的通用示例。delegate UiSafeCall(delegate d, params object p);
void SomeUiSafeCall(delegate d, params object p)
{
if (InvokeRequired)
BeginInvoke(d,p);
else
{
//do stuff to UI
}
}
这种方法基于委托引用特定实例上的方法;通过将实现作为表单的一个方法,您将表单作为this
引入范围。以下在语义上是相同的。
delegate UiSafeCall(delegate d, params object p);
void SomeUiSafeCall(delegate d, params object p)
{
if (this.InvokeRequired)
this.BeginInvoke(d,p);
else
{
//do stuff to UI
}
}