DirectShow / WPF Threading проблема - PullRequest
       6

DirectShow / WPF Threading проблема

4 голосов
/ 03 февраля 2011

Я пишу приложение, используя WPF и DirectShow, и столкнулся с проблемой. Мое приложение использует DS через статические методы Start () и Stop () в статическом классе, написанном с использованием DirectShowNet (класс-оболочка C # для DS). У меня есть панель Windows Forms в моем окне WPF (через объект WindowsFormsHost), для которой мне нужен график для визуализации. Вот общий ход приложения: метод Start () строит график и запускает его; Я передаю дескриптор моей панели формы Windows и отрисовываю ее с помощью интерфейса IVideoWindow. Start () возвращается и график работает в фоновом режиме. В какой-то момент вызывается Stop (); этот метод останавливает график и уничтожает его.

Все работает нормально, пока я вызываю Start () и Stop () из одного потока. Однако мне нужно будет вызывать их из разных потоков в моем приложении. Когда это так, я получаю исключение в части кода, которая уничтожает график (в частности, когда я пытаюсь перечислить фильтры). Я обнаружил, что мне нужно использовать многопоточные апартаменты при работе с DirectShow. Это легко с приложением Windows Forms; Я просто добавляю [MTAThread] в мой основной метод, и все работает.

Для моего приложения WPF это явно не вариант. Мой обходной путь состоял в том, чтобы запустить новые потоки MTA, когда мне нужно вызвать Start () и Stop (). Это избавляет от исключения, но создает еще одну проблему. Когда метод Start () возвращается, видео исчезает с панели рендеринга. Если я поставлю Sleep в конце метода Start (), видео будет видно до тех пор, пока Sleep не закончится. Кроме того, я проверил, что график продолжает работать после исчезновения видео. Кто-нибудь есть какие-либо советы о том, как действовать? Спасибо.

Кевин

Ответы [ 3 ]

1 голос
/ 03 февраля 2011

Какое исключение выдается?Я предполагаю что-то вроде: «Вызывающий поток не может получить доступ к этому объекту, потому что другой поток владеет им.»

В этом случае используйте правильный диспетчер для выполнения ваших вызовов, как объяснено здесь .

0 голосов
/ 03 февраля 2011

Хорошо, поэтому я столкнулся с проблемой, не слишком отличавшейся ранее, но не с WPF, поэтому примите следующее (очень смешное) предложение с щепоткой соли.

Следующий метод в основном создает совершенно отдельный поток приложения для запуска команд DirectShow, но указывает прямому шоу использовать дескриптор элемента управления Windows Form, размещенного в вашем приложении WPF.

Итак, сначала нам нужна фиктивная форма 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();

Преимущество этого подхода в том, что вы аккуратно помещаете песочницу в отдельную ветку. Недостатком является переключение контекста вызовов Invoke, а также накладные расходы на наличие другого потока приложения. Возможно, вам понравится добавить это в вашу текущую архитектуру, но это должно помочь.

Дайте мне знать, как вы поживаете, я заинтригован, насколько хорошо это работает.

0 голосов
/ 03 февраля 2011

К вашему сведению, Windows Forms не поддерживает основной поток MTAThread. Если это сработало, то вам просто повезло.

Полагаю, вы сможете нормально вызывать объекты DS из потоков STA - хотя я не очень знаком с DS, похоже, вы используете режим без окон , и мне кажется, что лучше всего будет работать с STA.

В таком случае, почему бы не всегда вызывать Start / Stop из вашего основного потока? Если другой поток должен указать основному потоку остановиться или начать, просто поставьте его в очередь на TaskScheduler.FromCurrentSynchronizationContext, чтобы запустить его в основном потоке.

...