Run method on UI thread from another thread

偶尔善良 提交于 2019-12-01 13:08:12

The SynchronizationContext class was meant to solve this problem. Copy the value of its Current property in the constructor, use its Post() or Send() method later. This ensures your library will work with any GUI class library. Like this:

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;
}

Pass a reference to the main dispatcher (=GUI-Thread's dispatcher) and call Invoke on it directly with your callback code.

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.
// ...

To be able to execute code on another thread, you must have a queue or message pump waiting for a new item to process.

This is already done in winforms and wpf via Control.Invoke and IDispatcher.Invoke. If you really want to avoid having the Control perform the listening, you'll have to pass the control into the MediaPlayer. It's really awkward, but there's a big complaint on SO that the first answer is "how about you stop doing that thing you're trying to do".. so here goes:

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);});
    }
}

Apologies if there's a typo, I don't have everything in front of me to verify with; but this should get you what you're after.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!