我有一节课,可以播放这样的音乐。它还在构造期间将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
?
我遇到的大多数解决方案建议我在监听OnTrackComplete
事件处理程序的方法中使用GUI代码中的调用。我想避免这样做。我想在MediaPlayer
类
<小时/> 根据接受的答案,这是我改变代码的方式
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);
}
}
答案 0 :(得分:7)
SynchronizationContext class旨在解决这个问题。在构造函数中复制其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;
}
答案 1 :(得分:1)
将引用传递给主调度程序(= GUI-Thread的调度程序),并使用您的回调代码直接调用它。
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.
// ...
答案 2 :(得分:0)
为了能够在另一个线程上执行代码,您必须有一个队列或消息泵等待处理新项目。
这已经通过Control.Invoke
和IDispatcher.Invoke
在winforms和wpf中完成。如果您真的想避免让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);});
}
}
道歉,如果有拼写错误,我没有在我面前的所有内容进行验证;但这应该可以让你得到你想要的东西。