简体   繁体   中英

Same thread re-enters method before leaving

We are attempting to read information from a hardware device, and the only way to do this is to communicate with it through a closed-source, native DLL the hardware manufacturer provides. They also provide a .NET wrapper to access the DLL, which the wrapper method of concern is simplified below:

[DllImport("hardware_mfg.dll")]
private static extern int hardware_command_unicode(MarshalAs(UnmanagedType.LPWStr)] string outdata, uint outcount, [MarshalAs(UnmanagedType.LPWStr)] StringBuilder indata, uint maxdata, ref uint incount);

public static int HardwareCommand(string senddata, StringBuilder recdata)
{
    uint incount = 0;
    return HardwareWrapper.hardware_command_unicode(senddata, (uint)senddata.Length, recdata, (uint)recdata.Capacity, ref incount);
}

The code calling the HardwareWrapper.HardwareCommand function is:

// This method gets called from a DispatcherTimer.Tick event
public static int SendCommand(string command, StringBuilder buffer)
{
    lock (_locker)
    {
        try
        {
            if (_commandInProgress)
            {
                // This exception gets thrown
                throw new InvalidOperationException("This should not be possible");
            }
            _commandInProgress = true;
            // InvalidOperationException gets thrown while previous call to HardwareCommand here has not yet returned
            var result = HardwareWrapper.HardwareCommand(command, buffer);
            return result;
        }
        finally
        {
            _commandInProgress = false;
        }
    }        
}

The confusing part is that the InvalidOperationException gets thrown. When the main thread enters the var result = HardwareWrapper.HardwareCommand(...) it is possible for the method to be called again, and enter the same function before the first call returns. It is intermittent that the exception gets thrown, but letting this code run for 15-30 seconds will be enough to have the exception happen.

  1. How is it possible for the main thread to exist twice in one method?
  2. What can be done to prevent this from happening?

EDIT 1: Move lock to outer scope

Without a good Minimal, Complete, and Verifiable code example it's impossible to provide a specific and complete diagnosis. But based on the information here, undoubtedly this is exactly as @David says : "your unmanaged code is pumping the queue" .

COM in particular is notorious for this, but it can occur in other ways. Typically, the native code enters some kind of wait state in which some or all messages for the thread are still dispatched. This can include the WM_TIMER message for the timer, causing the Tick event to be raised again, even before the previous event handler has returned.

Since it's in the same thread, the lock is irrelevant. The Monitor that is used by lock only blocks threads other than the one holding the lock; the current thread can re-enter any section of code protected by that monitor as often as it wants.

The message in your InvalidOperationException , "This should not be possible" , is incorrect. It is possible, and it should be possible. For better or worse, it's how messages in Windows work.

Depending on what your goal is and the specifics of the code involved (which you haven't provided), you have at least a couple of options:

  1. Don't use DispatcherTimer . Instead, use one of the other timer classes, which use the thread pool to raise timer events. These don't rely on a message queue and so pumping messages won't affect how the timer event is raised. Of course, this assumes you don't need to execute the code in the UI thread. Whether this is the case in your situation is not clear from the question. (Actually, it is possible to get this approach to work even if you do need to execute some code in the UI thread while holding the lock , but it gets tricky…better to avoid doing that if you can help it.)

  2. Use the _commandInProgress variable to detect the situation and ignore the timer event if the flag is already set to true . Of course, this assumes you don't need to execute the command on every timer event, and that there's some reasonable way to skip doing so (including dealing with the lack of a result value from the call to the native code). Again, there's not enough information in the question to know if this is the case.

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