从另一个线程在UI线程上运行方法

时间:2014-12-08 17:54:10

标签: c# multithreading

我有一节课,可以播放这样的音乐。它还在构造期间将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);
    }
}

3 个答案:

答案 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.InvokeIDispatcher.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);});
    }
}

道歉,如果有拼写错误,我没有在我面前的所有内容进行验证;但这应该可以让你得到你想要的东西。