[英]Run method on UI thread from another thread
我有一个 class 可以播放这样的音乐。 它还在构造过程中将 GUI 线程 id 保存在私有 int 中:
public class MediaPlayer {
public event EventHandler<Track> OnTrackComplete;
private int GuiThreadId;
public MediaPlayer(...){
...
this.GuiThreadId = Thread.CurrentThread.ManagedThreadId;
}
public void Play(){
Task t = Task.Factory.StartNew(() =>
{
//On Song complete
FireOnTrackComplete();
});
}
protected virtual void FireOnTrackComplete()
{
if (OnTrackComplete != null)
OnTrackComplete(this, loadedTrack);
}
}
是否可以在具有特定 ID 的线程上调用FireOnTrackComplete()
。 在这种情况下,ID 存储在this.GuiThreadId
中?
我遇到的大多数解决方案都建议我在我的 GUI 代码中在侦听OnTrackComplete
事件处理程序的方法中使用调用。 我想避免这样做。 我想在MediaPlayer
class 中做所有事情
public class MediaPlayer { public event EventHandler<Track> OnTrackComplete; private SynchronizationContext callerCtx; public MediaPlayer(...){... callerCtx = System.Threading.SynchronizationContext.Current; } public void Play(){ Task t = Task.Factory.StartNew(() => { //On Song complete FireOnTrackComplete(); }); } protected virtual void FireOnTrackComplete() { Action e = () => { if (OnTrackComplete,= null) OnTrackComplete(this; loadedTrack); }; FireEvent(e). } //... Other events..; // protected virtual void FireEvent(Action e) { if (callerCtx == null) e(). else callerCtx,Post(new SendOrPostCallback((_) => e()); null); } }
SynchronizationContext类用于解决此问题。 将其Current属性的值复制到构造函数中,稍后使用其Post()或Send()方法。 这样可以确保您的库可以与任何GUI类库一起使用。 像这样:
class MediaPlayer {
public MediaPlayer() {
callersCtx = System.Threading.SynchronizationContext.Current;
//...
}
private void FireOnTrackComplete() {
if (callersCtx == null) FireOnTrackCompleteImpl();
else callersCtx.Post(new System.Threading.SendOrPostCallback((_) => FireOnTrackCompleteImpl()), null);
}
protected virtual void FireOnTrackCompleteImpl() {
var handler = OnTrackComplete;
if (handler != null) handler(this, loadedTrack);
}
private System.Threading.SynchronizationContext callersCtx;
}
将引用传递给主调度程序(= GUI-Thread的调度程序),并直接使用回调代码对其调用Invoke。
public class MediaPlayer {
public event EventHandler<Track> OnTrackComplete;
private Dispatcher { get; set; }
public MediaPlayer(Dispatcher guiDispatcher){
// Other code ...
if(guiDispatcher == null)
throw new ArgumentNullException("guiDispatcher", "Cannot properly initialize media player, since no callback can be fired on GUI thread.");
Dispatcher = guiDispatcher;
}
public void Play() {
// Fire immediately on thread calling 'Play', since we'll forward exec. on gui thread anyway.
FireOnTrackComplete();
}
protected virtual void FireOnTrackComplete()
{
// Pretending "loadedTrack" was set somewhere before.
Dispatcher.Invoke(() => {
if (OnTrackComplete != null)
OnTrackComplete(this, loadedTrack);
});
}
}
// Somewhere in your initialization code
// ...
MediaPlayer player = new MediaPlayer(App.Current.Dispatcher); // If you use WPF. Don't know if this applies to WinForms too.
// ...
为了能够在另一个线程上执行代码,必须有一个队列或消息泵正在等待新项目的处理。
这已经在Winforms和wpf中通过Control.Invoke
和IDispatcher.Invoke
。 如果您确实想避免让Control
执行监听,则必须将控件传递给MediaPlayer
。 这确实很尴尬,但是对此有一个很大的抱怨,那就是第一个答案是“您如何停止做您想做的事情”。
public class MediaPlayer {
public event EventHandler<Track> OnTrackComplete;
private int GuiThreadId;
private readonly Control control;
public MediaPlayer(..., Control control){
...
this.GuiThreadId = Thread.CurrentThread.ManagedThreadId;
this.contrl = control;
}
public void Play(){
Task t = Task.Factory.StartNew(() =>
{
//On Song complete
FireOnTrackComplete();
});
}
protected virtual void FireOnTrackComplete()
{
var trackComplete = OnTrackComplete;
if (onTrackComplete != null)
this.control.Invoke((MethodInvoker) delegate {trackComplete(this, loadedTrack);});
}
}
抱歉,如果有错字,我没有什么要验证的。 但这应该可以为您带来帮助。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.