如何取消在STA线程中创建COM对象的等待?

How to cancel waiting on COM object creation within STA thread?

提问人:theateist 提问时间:11/10/2023 更新时间:11/10/2023 访问量:56

问:

我正在使用第三方 COM API。因此,我有创建COM对象的STA线程。问题在于,对象的创建可能需要几分钟左右的时间。我希望能够从外面取消它。我想按照 ATTEMPT 中所示包装它,但是我该如何等待它,并且由于它位于同一线程中,这不会造成死锁吗?new IThirdPartyClass()Task.Factory.StartNew(...);

这是正确的方法还是更好的方法?

尝试

var cancelTokenSource = new CancellationTokenSource();
var staThread = new Thread(() =>
{
   try
   {
      var comMessageFilter = CComMessageFilter.Create();
      if (comMessageFilter == null) return;
    
      Task.Factory.StartNew(()=>
      { 
        _thirdPartyObj = new IThirdPartyClass();
      }, cancelTokenSource.Token, TaskScheduler.FromCurrentSynchronizationContext());
     
      _thirdPartyObj.Do(...);
      ...
      
   }
   catch(...) {...}
});
staThread.IsBackground = true;
staThread.SetApartmentState(ApartmentState.STA);
staThread.Start();

原始代码

var staThread = new Thread(() =>
{
   try
   {
      var comMessageFilter = CComMessageFilter.Create();
      if (comMessageFilter == null) return;
    
      _thirdPartyObj = new IThirdPartyClass();

      ...
      _thirdPartyObj.Do(...);
      ...
      
   }
   catch(...) {...}
});
staThread.IsBackground = true;
staThread.SetApartmentState(ApartmentState.STA);
staThread.Start();

IThirdParty类

[ComImport]
[TypeLibType(TypeLibTypeFlags.FCanCreate)]
[ClassInterface(ClassInterfaceType.None)]
public class IThirdPartyClass : ...
{
    [MethodImpl(MethodImplOptions.InternalCall)]
    public extern IThirdPartyClass();

    ...
}

CComMessageFilter

class CComMessageFilter : IMessageFilter, IDisposable
{
    private const int RETRY_TIME_IN_MS = 200;

    [DllImport("Ole32.dll")]
    private static extern int CoRegisterMessageFilter(IMessageFilter newFilter, out IMessageFilter oldFilter);

    private IMessageFilter m_previousFilter;

    // =================================================================================================
    // Private Constructor. Immediately register the filter.
    // -------------------------------------------------------------------------------------------------
    private CComMessageFilter()
    {
        CoRegisterMessageFilter(this, out m_previousFilter);
    }

    // =================================================================================================
    // Creates a CComMessageFilter or null if creation failed.
    // Should be used like this: using ( CComMessageFilter.Create() ) {}
    // -------------------------------------------------------------------------------------------------
    public static CComMessageFilter Create()
    {
        if (Thread.CurrentThread.GetApartmentState() == ApartmentState.STA)
        {
            return new CComMessageFilter();
        }
        return null;
    }

    // =================================================================================================
    // Implementation of the IDisposable interface. Unregister the filter.
    // -------------------------------------------------------------------------------------------------
    public void Dispose()
    {
        IMessageFilter oldFilter = null;
        CoRegisterMessageFilter(m_previousFilter, out oldFilter);
    }

    // =================================================================================================
    // Implementation of IMessageFilter. See Microsoft documentation.
    // In our case we accept all calls.
    // -------------------------------------------------------------------------------------------------
    int IMessageFilter.HandleInComingCall(int dwCallType, IntPtr hTaskCaller, int dwTickCount, IntPtr lpInterfaceInfo)
    {
        return (int)SERVERCALL.SERVERCALL_ISHANDLED;
    }

    // =================================================================================================
    // Implementation of IMessageFilter. See Microsoft documentation.
    // In our case we always ask for a retry when it is possible.
    // -------------------------------------------------------------------------------------------------
    int IMessageFilter.RetryRejectedCall(IntPtr hTaskCallee, int dwTickCount, int dwRejectType)
    {
        if (dwRejectType == (int)SERVERCALL.SERVERCALL_RETRYLATER)
        {
            return RETRY_TIME_IN_MS;
        }

        return -1;
    }

    // =================================================================================================
    // Implementation of IMessageFilter. See Microsoft documentation.
    // -------------------------------------------------------------------------------------------------
    int IMessageFilter.MessagePending(IntPtr hTaskCallee, int dwTickCount, int dwPendingType)
    {
        return (int)PENDINGMSG.PENDINGMSG_WAITDEFPROCESS;
    }
}
C# 异步 COM 任务 STA

评论

1赞 Hans Passant 11/10/2023
当组件不支持运行时,无法中断正在运行的代码。
0赞 theateist 11/10/2023
有没有办法用其他线程(也许是其他线程)包装它,然后在当前线程中等待,直到它完成或取消?
0赞 Theodor Zoulias 11/10/2023
您的目标是什么 .NET 平台? .NET 7?
0赞 theateist 11/10/2023
@TheodorZoulias,目前我需要在 .Net Framework 4.72 中完成它,但我可能会在不久的将来将其转移到 .NET 6 或 7。
0赞 Theodor Zoulias 11/10/2023
看看这个:线程或任务(停止挂起的单行代码)。你要搜索的叫做“非合作取消”,它是一罐蠕虫

答: 暂无答案