Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Run method on UI thread from another thread

I have a class which plays some music like this. It also saves the GUI thread id in a private int during construction:

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

Is it possible to call FireOnTrackComplete() on a Thread with a specific ID. In this case, the ID is stored in the this.GuiThreadId?

Most of the solutions I have come across suggest I use invokes in my GUI code in methods which listen to the OnTrackComplete event handler. I want to avoid doing this. I want to do everything in the MediaPlayer class


Based on the accepted answer bellow this is how I changed my code
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);
    }
}
like image 621
Krimson Avatar asked Jun 13 '26 11:06

Krimson


1 Answers

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;
}
like image 192
Hans Passant Avatar answered Jun 16 '26 01:06

Hans Passant



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!