开发者

DirectShow/WPF Threading issue

I am writing an app using WPF and DirectShow and have run into a sticky issue. My application utilizes DS through static methods Start() and Stop() in a static class written using DirectShowNet (a C# wrapper class for DS). I have a Windows Forms panel in my WPF window (via a WindowsFormsHost object) that I need the graph to render to. Here is the general flow of the app: The Start() method builds the graph and starts it; I pass the handle of my windows form panel and render to it using the IVideoWindow interface. Start() returns and the graph runs in the background. At some point, Stop() is called; this method stops the graph and destroys it.

Everything works fine as long as I call Start() and Stop() from the same thread. However, I will need to call them from different threads in my app. When this is the case, I get an exception in the part of code that destroys the graph (specifically, when I am attempting to enum开发者_JAVA技巧erate the filters). I discovered that I need to use a Multithreaded Apartment when working with DirectShow. This is easy with a Windows Forms app; I just throw a [MTAThread] on my main method and everything works.

For my WPF app, this is apparently not an option. My workaround has been to launch new MTA threads when I need to call Start() and Stop(). This gets rid of the exception, but has introduced another problem. When the Start() method returns, the video disappears from the render panel. If I put a Sleep at the end of the Start() method, the video will be visible until the Sleep ends. In addition, I have verified that the graph continues to run after the video disappears. Does anyone have any advice as to how to proceed? Thanks.

Kevin


Which exception is thrown? I'm guessing something along the likes of: "The calling thread cannot access this object because a different thread owns it."

When this is the case, use a correct dispatcher to do your calls, as explained here.


FYI, Windows Forms doesn't support a MTAThread main thread. If it worked, then you just got lucky.

I believe you should be able to invoke DS objects from STA threads just fine - though I'm not that familiar with DS, it sounds like you're using windowless mode and it seems to me that it would work best with STA.

In that case, why not always call Start/Stop from your main thread? If another thread needs to tell the main thread to stop or start, then just have it queue a task to a TaskScheduler.FromCurrentSynchronizationContext to run it on the main thread.


Ok, so I've encountered a problem not too dissimilar before, but not with WPF, so take the following (very hacky) suggestion with a pinch of salt.

The following method basically creates an entirely separate application thread to run directshow commands in, but tells direct show to use the handle of the windows forms control hosted in your WPF application.

So, first we need a dummy WinForms form that we can use to invoke calls on, but that is never going to get rendered:

/// <summary>
/// Just a dummy invisible form.
/// </summary>
private class DummyForm : Form
{

    protected override void SetVisibleCore(bool value)
    {
        //just override here, make sure that the form will never become visible
        if (!IsHandleCreated)
        {
            CreateHandle();
        }

        value = false;
        base.SetVisibleCore(value);
    }
}

Next step is to create a thread that we can put a message loop on:

//this will need to be a class level variable, since all the directshow
//calls will get invoked on this form
DummyForm dumbForm;
Thread separateThread;


private void CreateDummyForm() 
{

    ManualResetEvent reset = new ManualResetEvent(false);

    //create our thread
    separateThread = new Thread((ThreadStart)
    delegate
    {

        //we need a dummy form to invoke on
        dumbForm = new DummyForm();

        //signal the calling method that it can continue
        reset.Set();

        //now kick off the message loop
        Application.Run(dumbForm);
    });

    //set the apartment state of this new thread to MTA
    separateThread.SetApartmentState(ApartmentState.MTA);
    separateThread.IsBackground = true;
    separateThread.Start();

    //we need to wait for the windowing thread to have initialised before we can 
    //say that initialisation is finished
    reset.WaitOne();

    //wait for the form handle to be created, since this won't happen until the form
    //loads inside Application.Run
    while (!dumbForm.IsHandleCreated)
    {
        Thread.Sleep(0);
    }

}

So, once the dummy form (and its thread) have been created, you can invoke calls on the MTA application thread like so:

/// <summary>
/// Blank delegate, used to represent any Invoke or BeginInvoke target method.
/// </summary>
public delegate void InvokeHandler();

//i'm assuming here that DSComponent is a class that all your directshow 
//code is in, and externalControl is the WinForms control you have embedded in 
//your application. 
dumbForm.Invoke(new InvokeHandler(delegate 
{
    //maybe something like this?
    DSComponent.Start(externalControl);
}));

//and to stop it...
dumbForm.Invoke(new InvokeHandler(delegate
{
    DSComponent.Stop();
}));

Then, when you're all done with the Directshow stuff, shutdown your separate application thread like so:

//to end the separate thread and application loop,
//just close your invisible form
dumbForm.Close();

Advantage of this approach is that you neatly sandbox directshow into a separate thread. Disadvantage is the context switch of the Invoke calls, plus the overhead of having another application thread. You may have some fun shoehorning this into your current architecture, but it should help.

Let me know how you get on, I am intrigued as to how well this works.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜