简体   繁体   中英

Prevent C# events to VB6 code to create a dead lock

I created a multithreading C# COM-Assembly, I used it from VB6.

The C#-COM can fire events from multiple threads, I created an invisible From-object and use this to synchronize all events before raise them.

if (myForm.InvokeRequired() )
{
  delOnMessage myDelegate = new delOnMessage(Message_received);
  myForm.Invoke(myDelegate, new object[] { null, null });
}
else
{
  RaiseMyEvent();
}

But if the VB6-Code is inside of an event handler and calls some methods of the COM-Object, this can produce a new event.

Private Sub m_SomeClass_SomeEvent(obj As Variant)
    COMobject.SendAnAnswer() ' This produces a new event
End Sub

In that case a part of the event-system stops working, suprisingly the Main VB6 Applikation still works.

EDIT: More detailed
If the C#-COM received a Message (from CAN-Bus-Thread) it creates an event, in some cases the VB6 calls a C#-COM method which creates an event, this event is reached the VB6 too.
But then the CAN-Bus-Thread seems to be blocked, as no more messages are received (till program restart).
But other events can occur.

The CAN-Bus-Thread is an endless loop to receive a message and fire an event.

I have two questions:

Is my way of synchronizing correct?
Is it possible without modifying the VB6-code to get it working?

Depending on the nature of the event, it may be sufficient simply to switch from Invoke to BeginInvoke , so that it is offloaded to the message-queue (without blocking, so no deadlock). It is convenient that Control.BeginInvoke (unlike Delegate.BeginInvoke ) does not require you to call EndInvoke , so you can use this in a fire-and-forget way.

I might be tempted to cut out some extra work, though:

myForm.BeginInvoke((MethodInvoker)RaiseMyEvent);

(ie jump direct to RaiseMyEvent )

I created an invisible From-object

That sounds like trouble. Using InvokeRequired is a dangerous anti-pattern. It is especially lethal with VB6, its runtime has badly broken thread handling. You know that the code is being called from a worker thread, use InvokeRequired only to verify that the form you use to synchronize is in the proper state to do so correctly:

if (!myForm.InvokeRequired()) {
    throw new InvalidOperationException("Synchronization window not created");
}
delOnMessage myDelegate = new delOnMessage(FireMessageReceivedEvent);
myForm.BeginInvoke(myDelegate, new object[] { null, null });

Odds are good that this exception will throw, creating an invisible form is not that easy. You can force the form's Handle property to be created by reading its Handle property. Or by overriding its SetVisibleCore() method to keep the form invisible:

    protected override void SetVisibleCore(bool value) {
        if (!this.IsHandleCreated) {
            this.CreateHandle();
            value = false;
        }
        base.SetVisibleCore(value);
    }

It is however very important that you call this form's Show() method on the main thread. It still won't work correctly if you create the form in your worker thread. There's no easy way to check for this in your code. Use the debugger and the Debug + Windows + Threads window to verify this.

Last but not least, do favor BeginInvoke() instead of Invoke(). This has much smaller odds of creating deadlock. This can cause problems by itself however, your worker thread may need to be throttled to prevent it flooding the main thread with invoke requests.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM