unosquare/ffmediaelement

FFME uses wrong dispatcher

markoweb2 opened this issue · 1 comments

FFME uses wrong dispatcher

I have a very similar scenario as described in here:
#384

In that I use a seperate thread with it's own dispatcher and window (call it customer display), inorder to show videos. This is so that any activity in the main GUI window and customer display window, would not interfere with each other or cause delays/stutters.

Because FFME uses the wrong dispatcher in my scenario (details follow), any action with it (for example Open() ), will cause an Exception - The calling thread cannot access this object because a different thread owns it.

I tracked down the bug in the source (GuiContext.cs) to:

private GuiContext()
{
        Thread = Thread.CurrentThread;
        ThreadContext = SynchronizationContext.Current;

        // Try to extract the dispatcher for the application
        try { GuiDispatcher = Application.Current.Dispatcher; } // **** USING THIS CAUSES THE EXCEPTION *****
        catch { /* Ignore error as app might not be available or context is not WPF */ }

        // If the above was unsuccessful, try to extract the dispatcher from the current thread.
        if (GuiDispatcher == null)
        {
            try { GuiDispatcher = Dispatcher.CurrentDispatcher; }
            catch { /* Ignore error as app might not be available or context is not WPF */ }
        }

        Type = GuiContextType.None;
        if (GuiDispatcher != null) Type = GuiContextType.WPF;
        else if (ThreadContext is WindowsFormsSynchronizationContext) Type = GuiContextType.WinForms;

        IsValid = Type != GuiContextType.None;
}

Notice that the first dispatcher to be used is Application.Current.Dispatcher.
In my scenario this would always be the main GUI dispatcher. But in fact I have created a seperate thread with it's own disaptcher and FFME would need to use this other dispatcher.

I don't quite understand the logic, why the default option is to use Application.Current.Dispatcher. (can't think of a scenario where that would be the correct option)
I think it should be the other way around.
That GuiContext constructor would first try to use the current thread's dispatcher. If that doesn't exist, then try to use Application.Current.Dispatcher.
(although I'm starting to think this would not help, because the class Library is static and it's GuiContext is set in the class MediaElement static constructor, because static elements get constructed before any instances, then there is no hope in getting the correct dispatcher ever...)

Or at the very least, give us an option to provide the correct dispatcher.
Currently I have to use a very ugly reflection workaround (in my customer display window Loaded event):

var libraryType = (typeof(Unosquare.FFME.Library));
var field1 = libraryType.GetField("m_GuiContext", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);
object m_GuiContext = field1.GetValue(null);

var field2 = m_GuiContext.GetType().GetField("<GuiDispatcher>k__BackingField", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
field2.SetValue(m_GuiContext, this.Dispatcher);

Hmm, actually the WPF control itself always has a reference to the correct dispatcher, from where this control was instantiated.
So why not use the controls own dispatcher, why store the dispatcher in a static field, before the control has been constructed?

For example, in the class MediaConnector, all the OnMedia events.
Instead of using Library.GuiContext.EnqueueInvoke(async () =>, you should instead use something like this.Parent.Dispather.Invoke(). (this.Parent would refer to the FFME MediaElement WPF control)

Issue Categories

  • Bug
  • Feature Request
  • Question
  • Not sure

Version Information

  • NuGet Package 4.4.350

Steps to Reproduce

  1. Create a new WPF project
  2. Add a window, call it Window2
  3. In Window2 use the FFME control (name = media), in the Window2_Loaded() event handler for example, write media.Open(...)
  4. In MainWindow, add a button, in the click handler write:
Thread tcd = new Thread(() => { var w2 = new Window2(); w2.Show(); System.Windows.Threading.Dispatcher.Run(); } );
tcd.SetApartmentState(System.Threading.ApartmentState.STA);
tcd.Start();
  1. Run the app and click the button = crash, The calling thread cannot access this object because a different thread owns it.

Expected Results

  • I expect the app to not crash in this scenario. FFME should use the correct dispatcher related to it's MediaElement control.
stale commented

This issue has been automatically marked as stale because it has not had recent activity. Thank you for your contributions.