2016-08-22 1 views
0

Wir versuchen, Informationen von einem Hardwaregerät zu lesen, und die einzige Möglichkeit, dies zu tun, besteht darin, über eine geschlossene native DLL, die der Hardwarehersteller bereitstellt, mit ihr zu kommunizieren. Sie bieten auch eine .NET-Wrapper die DLL zuzugreifen, die die Wrappermethode Besorgnis unter vereinfacht:Gleicher Thread gibt die Methode vor dem Verlassen erneut ein

[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); 
} 

Der Code der HardwareWrapper.HardwareCommand Aufruf der Funktion ist:

// 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; 
     } 
    }   
} 

Das verwirrende Teil ist dass die InvalidOperationException ausgelöst wird. Wenn der Hauptthread in var result = HardwareWrapper.HardwareCommand(...) eintritt, ist es möglich, die Methode erneut aufzurufen und die gleiche Funktion einzugeben, bevor der erste Aufruf zurückgegeben wird. Es ist zeitweise, dass die Ausnahme ausgelöst wird, aber die Ausführung dieses Codes für 15-30 Sekunden reicht aus, um die Ausnahme passieren zu lassen.

  1. Wie ist es möglich, dass der Haupt-Thread zweimal in einer Methode existiert?
  2. Was kann getan werden, um dies zu verhindern?

EDIT 1: Verschieben Sperre äußeren Umfang

+0

Wiederkehrende Anrufe sind ziemlich normal. Funktion A ruft Funktion B auf, die Funktion A aufruft. Das ist, was Sie haben. Vermutlich führt 'HardwareWrapper.HardwareCommand' dazu, dass das Timer-Tick-Ereignis erneut ausgelöst wird. –

+0

HardwareWrapper.HardwareCommand ist nicht verwalteter Code und ruft SendCommand nicht auf oder löst das Timer-Tick-Ereignis aus. Das DispatcherTimer-Ereignis kann erneut ausgelöst werden, während sich der Hauptthread im nicht verwalteten Code befindet. Aber sollte der Hauptthread nicht warten, bis der nicht verwaltete Code zurückgegeben wird, bevor diese Methode erneut aufgerufen wird? – Clark

+0

Sind Sie sicher, dass es keine Wettlaufbedingung ist, wenn die 'Tick' erneut ausgeführt wird, bevor' finally' eingegeben und das Flag zurückgesetzt wird? Versuchen Sie, das Flag innerhalb der 'Sperre' zurückzusetzen – slawekwin

Antwort

1

Ohne eine gutes Minimal, Complete, and Verifiable code example es ist unmöglich, eine spezifische und vollständige Diagnose zu liefern. Aber basierend auf den Informationen hier, ist dies zweifellos genau as @David says: "Ihr unmanaged Code pumpt die Warteschlange".

Insbesondere COM ist dafür berüchtigt, aber es kann auf andere Arten auftreten. In der Regel tritt der systemeigene Code in eine Art von Wartezustand ein, in dem einige oder alle Nachrichten für den Thread noch gesendet werden. Dies kann die WM_TIMER-Nachricht für den Timer enthalten, wodurch das Ereignis Tick erneut ausgelöst wird, noch bevor der vorherige Ereignishandler zurückgegeben wurde.

Da es im selben Thread ist, ist die lock irrelevant. Die Monitor, die von lock verwendet wird, blockiert nur Threads andere als die, die das Schloss hält; Der aktuelle Thread kann so oft wie gewünscht in jeden von diesem Monitor geschützten Codeabschnitt zurückkehren.

Die Nachricht in Ihrem InvalidOperationException, "Dies sollte nicht möglich sein", ist falsch. Es ist möglich, und es sollte möglich sein. Zum Besseren oder Schlechteren funktionieren Nachrichten in Windows.

Je nachdem, was Ihr Ziel ist und die Besonderheiten des Codes beteiligt (die Sie nicht zur Verfügung gestellt haben), müssen Sie mindestens ein paar Optionen:

  1. nicht DispatcherTimer anwenden. Verwenden Sie stattdessen eine der anderen Timer-Klassen, die den Thread-Pool verwenden, um Timer-Ereignisse zu erhöhen. Diese basieren nicht auf einer Nachrichtenwarteschlange und das Pumpen von Nachrichten hat keinen Einfluss darauf, wie das Timer-Ereignis ausgelöst wird. Dies setzt natürlich voraus, dass Sie den Code im UI-Thread nicht ausführen müssen. Ob dies in Ihrer Situation der Fall ist, ist aus der Frage nicht ersichtlich. (Eigentlich ist es möglich, diesen Ansatz zu funktionieren, auch wenn Sie Code im UI-Thread ausführen müssen, während Sie die lock halten, aber es wird schwierig und hellip; besser, dies zu vermeiden, wenn Sie ihm helfen können.)

  2. Verwenden Sie die Variable _commandInProgress, um die Situation zu erkennen, und ignorieren Sie das Timer-Ereignis, wenn das Flag bereits auf true gesetzt ist. Dies setzt natürlich voraus, dass Sie den Befehl nicht bei jedem Timer-Ereignis ausführen müssen und dass es einen vernünftigen Weg gibt, dies zu überspringen (einschließlich des Mangels an einem Ergebniswert aus dem Aufruf des nativen Codes). Auch hier gibt es nicht genug Informationen, um zu wissen, ob dies der Fall ist.

+0

Um fehlende Details hinzuzufügen, 1. diese 3rd-Party-Hardware-DLL überwacht, welche Thread-Kommunikation mit ihm herstellt, und dann alle zukünftige Kommunikation ** muss ** auf demselben Thread auftreten . Wir verwenden den UI-Thread für Starter (daher der 'DispatcherTimer'), und wenn wir es vollständig funktionieren lassen können, verschiebt sich alles auf eine lang laufende Hintergrund-' Thread() '-Instanz. 2. Nicht alle Timer-Ereignisse erfordern die Ausführung von Befehlen, aber leider _some_ tun. Wo kann ich mehr über "Ihr nicht verwalteten Code pumpt die Warteschlange" lernen – Clark

+0

@King: _ "überwacht, welcher Thread die Kommunikation mit ihm herstellt, und dann muss die gesamte zukünftige Kommunikation auf dem gleichen Thread stattfinden" _ - das klingt sehr nach a STA-Modell COM-Objekt. Möglicherweise müssen Sie einen alternativen Thread explizit für STA festlegen, wenn Sie das Objekt in einem anderen Thread verwenden (dies wird von Winforms oder WPF für den UI-Thread erledigt). –

+0

Auf der hellen Seite, in diesem Fall könnten Sie Ihren Timer einfach mithilfe von 'Thread.Sleep()' implementieren - nicht ideal, aber da Sie den Thread nichts anderes tun müssen, sollte nicht so schlimm sein. Mehr noch, es würde die Re-Entrance vermeiden. Sie finden http://stackoverflow.com/questions/21211998/stataskscheduler-and-sta-thread-message-pumping und https://blogs.msdn.microsoft.com/cbrumme/2004/02/02/apartments-and pumpen-in-der-clr/nützlich. Weitere Details finden Sie auf MSDN. –

Verwandte Themen