DirectShow/WPF 多线程问题

4
我正在使用WPF和DirectShow编写一个应用程序,遇到了一个棘手的问题。我的应用程序通过使用DirectShowNet(DS的C#封装类)编写的静态类中的静态方法Start()和Stop()来利用DS。我在我的WPF窗口中有一个Windows Forms面板(通过WindowsFormsHost对象),需要将图形呈现到该面板上。以下是应用程序的一般流程:Start()方法构建图形并启动它;我传递我的Windows表单面板的句柄,并使用IVideoWindow接口将其呈现。Start()返回后,图形在后台运行。在某个时刻,调用Stop()方法;此方法停止图形并销毁它。
只要我从同一个线程中调用Start()和Stop(),一切都正常。但是,在我的应用程序中,我需要从不同的线程中调用它们。这种情况下,当我试图枚举过滤器时,在销毁图形的代码部分中会出现异常。我发现在使用DirectShow时需要使用多线程公寓(Multithreaded Apartment)。对于Windows Forms应用程序来说很容易;我只需在我的主方法上添加[MTAThread],就可以使一切顺利进行。
对于我的WPF应用程序,显然不是这样的选择。我的解决方法是在需要调用Start()和Stop()时启动新的MTA线程。这消除了异常,但引入了另一个问题。当Start()方法返回时,视频从渲染面板中消失。如果我在Start()方法的末尾放置一个Sleep,视频将一直可见,直到Sleep结束。此外,我已经验证图形在视频消失后继续运行。有人有什么建议吗?谢谢。
Kevin

1
发布代码还是发布代码的描述?我会选择发布代码。 - Mitch Wheat
2
你为什么要使用DirectShow?WPF可以播放媒体。 - Emond
2
@Emo - 也许他有一个不是文件或正常视频流的视频源?他可能有某种自定义源过滤器,我以前遇到过这种情况。 - Alistair Evans
3个回答

1
哪个异常会被抛出?我猜应该是类似于:“调用线程无法访问此对象,因为另一个线程拥有它。”
当出现这种情况时,请使用正确的调度程序进行调用,如此处所述。

0

好的,所以我之前遇到过一个类似的问题,但不是与WPF有关的,因此请带着一点怀疑的态度接受以下(非常hacky)建议。

以下方法基本上创建了一个完全独立的应用程序线程来运行直接显示命令,但告诉直接显示使用托管在您的WPF应用程序中的Windows窗体控件的句柄。

因此,首先我们需要一个虚拟的WinForms表单,我们可以用它来调用调用,但永远不会被呈现:

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

下一步是创建一个线程,我们可以在其上放置消息循环:
//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);
    }

}

因此,一旦创建了虚拟表单(及其线程),您可以像这样调用 MTA 应用程序线程上的调用:

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

然后,当您完成Directshow相关工作后,可以通过以下方式关闭单独的应用程序线程:

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

这种方法的优点是将DirectShow清晰地隔离到一个单独的线程中。缺点是Invoke调用的上下文切换以及拥有另一个应用程序线程的开销。你可能需要花费一些时间将其嵌入到当前架构中,但它应该会有所帮助。

让我知道你的进展如何,我很想知道这个方法的效果如何。


0

请注意,Windows Forms不支持MTAThread主线程。如果它能工作,那么你只是走了运。

我相信您应该可以从STA线程中调用DS对象 - 虽然我对DS不是很熟悉,但听起来您正在使用无窗口模式,我认为它最适合与STA一起使用。

在这种情况下,为什么不总是从主线程调用Start/Stop呢?如果另一个线程需要告诉主线程停止或启动,则只需将其排队到TaskScheduler.FromCurrentSynchronizationContext以在主线程上运行它。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接