Ошибка "Невозможно привести COM-объект типа" при пересечении потоковых квартир в C#
Я работал со сторонним SDK в качестве ссылочной библиотеки DLL из C#, используя.NET 4.5.2 в Windows 10. Среда IDE создает Interop вокруг библиотеки DLL, и я вижу соответствующие пространства имен, интерфейсы, перечисление и т. Д. SDK, с которым я работаю, предназначен для Blackmagic ATEM Television Studio, но я не думаю, что это имеет непосредственное отношение.
SDK предоставляет объект, который я могу использовать для обнаружения / обнаружения подключенного оборудования, и возвращает экземпляр объекта, который выглядит как оболочка (RCW) вокруг базового COM-объекта.
Код обнаружения выглядит следующим образом:
string address = "192.168.1.240";
_BMDSwitcherConnectToFailure failureReason = 0;
IBMDSwitcher switcher = null;
var discovery = new CBMDSwitcherDiscovery();
discovery.ConnectTo(address, out switcher, out failureReason);
Если я выполню этот код в своем потоке пользовательского интерфейса WPF, который является квартирой STA, он будет работать без каких-либо проблем. Я могу использовать объект без ошибок. Однако, если я попытаюсь получить доступ к полученному объекту переключателя из любого нового потока (STA или MTA), я получу ошибку, подобную следующей:
Невозможно преобразовать COM-объект типа "System.__ComObject" в интерфейсный тип "BMDSwitcherAPI.IBMDSwitcherMixEffectBlock". Эта операция завершилась неудачно, поскольку вызов QueryInterface для компонента COM для интерфейса с IID '{11974D55-45E0-49D8-AE06-EEF4D5F81DF6}' завершился ошибкой из-за следующей ошибки: такой интерфейс не поддерживается (Исключение из HRESULT: 0x80004002 (E_NOINTERFACE)),
Цитата из MikeJ и его ответ на аналогичный вопрос здесь:
Это неприятное, неприятное исключение возникает из-за концепции, известной как COM-маршаллинг. Суть проблемы заключается в том, что для использования COM-объектов из любого потока поток должен иметь доступ к информации о типе, которая описывает COM-объект.
На этом этапе я решил немного адаптировать свою стратегию и попытаться вызвать логику обнаружения из потока MTA. Как это:
private IBMDSwitcher _switcher = null;
public void Connect()
{
Task.Run(() =>
{
string address = "192.168.1.240";
IBMDSwitcherDiscovery discovery = new CBMDSwitcherDiscovery();
_BMDSwitcherConnectToFailure failureReason = 0;
discovery.ConnectTo(address, out _switcher, out failureReason);
});
}
Кажется, это работает, но за плату.
Компромисс в том, что теперь я могу без проблем работать со своим объектом переключателя из любого фонового потока (MTA), но все мое приложение просто рухнет после запуска в течение примерно 15 минут. Прежде чем кто-то скажет это, я знаю, что это не потокобезопасно. Я осознаю необходимость некоторой логики синхронизации, прежде чем несколько потоков MTA попытаются использовать мой объект переключателя. Но это в будущем, сейчас, если я запусту приведенный выше код и просто оставлю приложение бездействующим на 15 минут, оно каждый раз будет падать.
Итак, с одной стороны, у меня есть стабильное приложение, но я могу использовать только библиотеку из потока пользовательского интерфейса, а с другой стороны, у меня нестабильное приложение, но я могу использовать библиотеку из любого (MTA) фонового потока.
Из-за характера моего приложения было бы предпочтительно иметь возможность использовать библиотеку из нескольких фоновых потоков. Например, один поток может генерировать и загружать графику на устройство, в то время как другой поток управляет переключением видеовходов, а третий поток управляет аудиовходами, а поток пользовательского интерфейса обрабатывает необязательные диалоги ID-10T... Все это может быть сделано из потока пользовательского интерфейса, но я бы действительно предпочел этого избежать, если это возможно.
И так: есть ли способ, которым я могу обнаружить и создать свой объект переключателя в потоке пользовательского интерфейса (STA) (и теоретически избежать взрыва через 15 минут), но сделать информацию о типе доступной для моих фоновых (MTA) потоков, чтобы они также могли использовать переключатель? Или, наоборот, безопасно ли обнаруживать / создавать мой объект переключателя в фоновом потоке (MTA), но мне нужно что-то сделать, чтобы "пометить" его каким-то особым образом, чтобы избежать этого 15-минутного разрыва?
РЕДАКТИРОВАТЬ:
После прочтения комментария Ханса Пассанта и изучения предоставленной им ссылки я адаптировал указанный класс STAThread для своего собственного использования в WPF. Но я немного неясен с SynchronizationContext и Dispatcher. Будет ли мой код, перечисленный ниже, безопасным для использования в качестве оболочки вокруг моего COM-объекта, не блокируя пользовательский интерфейс во время каких-либо "длительных" операций? Мой вариант использования мог бы состоять в том, чтобы запустить Task(), который подготовит некоторые данные, затем вызвать некоторый код для взаимодействия с моим COM-объектом, и когда операция завершится, поток кода возобновится в выполняющейся задаче для выполнения следующей серии операций.,
public class STAThread
{
private Thread thread;
private SynchronizationContext ctx;
private ManualResetEvent mre;
public STAThread()
{
using (mre = new ManualResetEvent(false))
{
thread = new Thread(() =>
{
ctx = new SynchronizationContext();
mre.Set();
Dispatcher.Run();
});
thread.IsBackground = true;
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
mre.WaitOne();
}
}
public void BeginInvoke(Delegate dlg, params Object[] args)
{
if (ctx == null) throw new ObjectDisposedException("STAThread");
ctx.Post((_) => dlg.DynamicInvoke(args), null);
}
public object Invoke(Delegate dlg, params Object[] args)
{
if (ctx == null) throw new ObjectDisposedException("STAThread");
object result = null;
ctx.Send((_) => result = dlg.DynamicInvoke(args), null);
return result;
}
}