2009-04-10 2 views
2

Ich habe eine Anwendung, die mit einer vom Hersteller bereitgestellten API arbeiten muss, um Screen Pops auszuführen, wenn ein Anruf an die Nebenstelle des Benutzers weitergeleitet wird. Ein anderer Entwickler arbeitete daran, die API zum Laufen zu bringen und lieferte eine Prototyp-Wrapper-Klasse, die nicht ganz richtig ist, und ich versuche, es richtig zu machen.Wie man mit dem Starten/Stoppen der API-Schnittstelle umgeht, die lange dauern kann

Da diese API für einige Minuten vor der Rückkehr blockieren kann, begannen die Entwickler den API-Schnittstelle Code in einem separaten Thread, wie folgt aus:

// constructor 
public ScreenPop(...params...) 
{ 
    ... 
t = new Thread(StartInBackground); 
t.Start(); 
) 

private void StartInBackground() 
{ 
    _listener = new ScreenPopTelephoneListener(); 
    _bAllocated = true; 
    _listener.Initialize(_address); 
    _bInitialized = true; 
    _listener.StartListening(_extension); 
    _bListening = true; 
} 

Der Aufruf zum Initialisieren ist derjenige, hängen könnte, wenn die Der Telefonserver reagierte nicht.

Ich glaube nicht, dass es von Seiten des Entwicklers absichtlich gemacht wurde, aber wenn die API auf diese Weise initialisiert wird, läuft der Thread weiter, so dass ich jetzt den Thread stoppen muss, bevor ich aufräumen kann Wenn ich will. Ich muss auch Invoke verwenden, um Ereignisse aus dem Thread zu bekommen, wie andere in meiner vorherigen Frage so freundlich darauf hingewiesen haben, aber das funktioniert jetzt.

Wie auch immer, hier ist die Logik verwendet, um alles zu beenden und aufzuräumen:

public void ShutdownFunc() 
{ 
    try 
    { 
    if (_bListening) 
    { 
     _listener.StopListening(); 
     _bListening = false; 
    } 
    if (_bInitialized) 
    { 
     _listener.Shutdown(); 
     _bInitialized = false; 
    } 
    if (_bAllocated) 
    { 
     _listener = null; 
     _bAllocated = false; 
    } 
    } 
} 

Natürlich gibt es ein try/catch um den ganze Schlamassel Ausnahmen zu verhindern, nicht behandelte.

Mein größtes Problem ist, wie kann ich den Shutdown-Code aufrufen, wenn der API-Schnittstellencode in einem separaten Thread ausgeführt wird? Ich muss aufräumen, wenn sich der Benutzer abmeldet und sich dann wieder anmeldet, da die API sonst verwirrt wird. Thread.Abort funktioniert nicht, da es sich nicht an einem Ort befindet, an dem die Ausnahme abgefangen werden kann, und die "volatile bool" -Methode funktioniert auch nicht, aus offensichtlichen Gründen (meistens ist der API-Thread nicht aktiv).

Es scheint also keine Möglichkeit zu geben, die Abschaltlogik aufzurufen, wenn der API-Schnittstellencode in einem separaten Thread ausgeführt wird. Thread.Abort() umgeht die gesamte Logik und beendet den Thread ohne zu bereinigen. Der Aufruf der Shutdown-Logik vom GUI-Thread hängt beim Aufruf der Shutdown-Methode. Was kann ich also tun?

Die IDispose-Schnittstelle fügt nur eine weitere Abstraktionsebene in den GUI-Thread ein und löst das Problem hier nicht.

Für jetzt, um den Rest des Codes entwickelt zu bekommen, beseitige ich Threads vollständig. Bevor ich die App freigebe, muss ich das herausfinden.

Jeder?

Danke,
Dave

Antwort

0

Mit Hilfe eines Kollegen Entwickler hier, konnte ich den Code wie gewünscht arbeiten. Was ich tun musste, war, alles außer dem Aufruf von Initialize() aus dem Thread herauszunehmen, sodass die API-Instanz nicht in einem separaten Thread ausgeführt wurde. Ich hatte dann ein anderes Problem, da der Status von 2 verschiedenen Threads gesetzt wurde und nicht das war, was ich erwartet hatte, als ich aufräumte.

Hier ist die endgültige Lösung:

// declare a background worker to init the API 
private BackgroundWorker _bgWorker; 

// ctor 
public HiPathScreenPop(...) 
{ 
    ... 

    _phoneListener = new ScreenPopTelephoneListener(); 
    _apiState = ApiState.Allocated; 

    _phoneListener.StatusChanged += new _IScreenPopTelephoneListenerEvents_StatusChangedEventHandler(StatusChangedEvent); 
    _phoneListener.ScreenPop += new _IScreenPopTelephoneListenerEvents_ScreenPopEventHandler(ScreenPopEvent); 

    _bgWorker = new BackgroundWorker(); 
    _bgWorker.DoWork += new DoWorkEventHandler(StartInBackground); 
    _bgWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgWorker_RunWorkerCompleted); 

    _bgWorker.RunWorkerAsync(); 
} 

void _bgWorker_DoWork(object sender, DoWorkEventArgs e) 
{ 
    _phoneListener.Initialize(_strAddress); 
} 

void bgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 
{ 
    _apiState = ApiState.Initialized; // probably not necessary... 

    _phoneListener.StartListening(_intExtension.ToString()); 
    _apiState = ApiState.Listening; 
} 

Nun wird die API-Instanz glücklich im selben Thread wie die UI und jeder läuft. Der Aufruf von Shutdwn() hängt nicht mehr, ich brauche Invoke() nicht für die Rückrufe, und der lange laufende Code wird in einem Hintergrund-Worker-Thread ausgeführt.

Was dieser Code nicht zeigt, sind die großartigen Ideen, die zuvor für die Behandlung des Falls vorgestellt wurden, in dem sich der Benutzer vor dem Aufruf von Initialize() beendet. Der endgültige Code wird eine Synthese dieser Lösungen sein.

Vielen Dank an alle für das tolle Feedback!

Dave

1

Sie können weitere Initialisierung Logik und überprüfen auf einem Manual im Haupt Initialisierung Thread einkapseln, während ein Arbeiter-Thread Laichen die tatsächliche Initialisierung zu tun. Wenn ManualResetEvent während der Initialisierungsphase von Ihrem Hauptthread aus festgelegt wird, können Sie den Thread abbrechen, der die eigentliche Initialisierung ausführt, und die Bereinigung durchführen. Wenn der Worker-Thread andernfalls beendet wird, hören Sie einfach auf, auf ManualResetEvent zu warten. Es ist irgendwie schmutzig und kompliziert, sollte aber tun, was es soll;)

+0

Das würde funktionieren, wenn die Initialize die einzige Sache in dem neuen Thread war. Der Thread läuft weiter, und ich muss wissen, wie ich ihn stoppen kann ... – DaveN59

+0

Was ich vorgeschlagen habe, bestand darin, Initialize und StartListening zu teilen, damit nach der Initialisierung nur der Hintergrund-Thread zuhören kann. Die Struktur ist Haupt -> Arbeiter -> Initialisierer. Der Initialisierer wird beendet und der Mitarbeiter muss die Daten abhören und verarbeiten. – em70

+0

Ich bekomme das, und es ist eine gute Idee, aber ich habe immer noch das Problem, wie man den Arbeitsthread herunterfährt. Ich bekomme das noch nicht, und es gibt keine Möglichkeit, eine halbe Antwort zu geben ... – DaveN59

0

Vor allem, wenn Sie Ihre Anwendung herunterzufahren und Initialize() läuft noch, haben Sie nur zwei Möglichkeiten:

  1. Warten Sie auf 'Initialize', um zu beenden
  2. Brechen Sie den laufenden Thread 'Initialize' ab.

den Faden abbrechen sollte immer die letzte Option sein, aber falls Sie wirklich brauchen, um die Anwendung zu beenden und die Initialize keine Timeout-Parameter, gibt es keinen anderen Weg.

Auch würde ich vorschlagen, diese 3rd-Party-API in Ihrer eigenen Klasse zu implementieren, IDisposable darauf implementieren und halten Sie den API-Status mit einigen Enum anstelle von separaten Bool-Flags wie Sie in ShutdownFunc getan. Fügen Sie einige einfache Automationsmaschinenlogik hinzu, um jeden Zustand zu behandeln. Und halten Sie die möglichen Staaten zählen auf Minimum benötigt.

oder möglicherweise die Lieferanten-Bibliothek in dem Müll werfen kann;)

+0

Zunächst ist der Code hier die Wrapper-Klasse - die Aufrufe von _listener.was auch immer Aufrufe an die API-Bibliothek von Drittanbietern sind. Zweitens fragte ich, ob Dispose der richtige Weg ist, um damit umzugehen, was Sie anscheinend zu sagen scheinen. Ich mag die Idee des Staates statt mehrere Bools, obwohl – DaveN59

+0

IDisposable ist immer der richtige Weg zur Entsorgung von Ressourcen, aber das Problem ist, was passiert, wenn Sie Dispose aufrufen, wenn Ihr anderer Thread auf Initialize() wartet. Hier kommt die Thread-Synchronisation ins Spiel (siehe Antwort von emaster). –

+0

Wie mein Kommentar zu emaster, das ist toll, wenn das einzige, was in dem neuen Thread getan wurde, Initialize war. Der Thread läuft weiter und ich muss herausfinden, wie ich ihn herunterfahren kann, kurz vor dem Abbruch. – DaveN59

0

überarbeitete ich die Logik für die Shutdown-Funktion, zu der Handvoll booleans herum schwimmen loszuwerden. Hier ist, was der neue Code sieht so aus:

private enum ApiState { Unallocated, Allocated, Initialized, Listening }; 
private ApiState _apiState = ApiState.Unallocated; 

private void StartInBackground() 
{ 
    _listener = new ScreenPopTelephoneListener(); 
    _apiState = ApiState.Allocated; 
    _phoneListener.Initialize(_strAddress); 
    _apiState = ApiState.Initialized; 
    _phoneListener.StartListening(_intExtension.ToString()); 
    _apiState = ApiState.Listening; 
} 

public void ShutdownFunc 
{ 
    try 
    { 
    if (ApiState.Listening == _apiState) 
    { 
     _listener.StopListening(); 
     _listener.Shutdown(); 
    } 
    if (ApiState.Initialized == _apiState) 
    { 
     _listener.Shutdown(); 
    } 
    } 
    catch {} 
    finally 
    { 
    _listener = null; 
    _apiState = ApiState.Unallocated; 
    } 
} 

Das Problem bleibt jedoch, wo ich nicht die Shutdown-Logik aufrufen kann, ohne den GUI-Thread zu hängen. Gibt es eine Möglichkeit, den Worker-Thread mit einem Aufruf aus dem GUI-Thread zu unterbrechen, kurz vor Abbruch? Kann ich möglicherweise einen Event-Handler einrichten und ein manuell generiertes Ereignis aus dem GUI-Thread verwenden, um den Worker-Thread zu stoppen? Hat jemand das versucht?

Ich habe die Vorschläge zum Starten eines neuen Threads für den Initialisierungsaufruf nicht ausprobiert, da dies nur einen Teil des Problems löst und möglicherweise unnötig ist, sobald ich das eigentliche Problem gelöst habe.

0

OK, hier geht ein weiterer Versuch. Ich habe versucht, ein Ereignis zu deklarieren und dieses Ereignis im Thread anzuwerfen, um die Abschaltlogik von dem Thread auszuführen, auf dem die API läuft. Kein Glück, es versucht immer noch, die Abschaltlogik auf dem GUI-Thread auszuführen und hängt die Anwendung.

Hier ist der entsprechende Code:

public delegate void HiPathScreenPop_ShutdownEventHandler(object parent); 

class HiPathScreenPop 
{ 
    // Shutdown event, raised to terminate API running in separate thread 
    public event HiPathScreenPop_ShutdownEventHandler Shutdown; 
    private Thread _th; 

    //ctor 
    public HiPathScreenPop() 
    { 
     ... 
     _th = new Thread(StartInBackground); 
     _th.Start(); 
    } 

    private void StartInBackground() 
    { 
     _phoneListener = new ScreenPopTelephoneListener(); 
     _apiState = ApiState.Allocated; 

     _phoneListener.StatusChanged += new _IScreenPopTelephoneListenerEvents_StatusChangedEventHandler(StatusChangedEvent); 
     _phoneListener.ScreenPop += new _IScreenPopTelephoneListenerEvents_ScreenPopEventHandler(ScreenPopEvent); 
     this.Shutdown += new HiPathScreenPop_ShutdownEventHandler(HiPathScreenPop_Shutdown); 

     _phoneListener.Initialize(_strAddress); 
     _apiState = ApiState.Initialized; 

     _phoneListener.StartListening(_intExtension.ToString()); 
     _apiState = ApiState.Listening; 
    } 

    public void KillListener() 
    { 
     OnShutdown(); 
    } 

    private void OnShutdown() 
    { 
     if (this.Shutdown != null) 
     { 
      this.Shutdown(this); 
     } 
    } 

    void HiPathScreenPop_Shutdown(object parent) 
    { 
     // code from previous posts 
    } 
} 

Was, wenn ich passieren scheint nennen KillListener ist dies:

1) OnShutdown() wird auf dem UI-Thread (OK genannt)

2) Shutdown (this) wird aufgerufen (hier ist, wo ich erwarte, dass ein Ereignis im Hintergrund Thread ausgelöst wird

3) HiPathScreenPop_Shutdown() wird auf dem UI-Thread aufgerufen, und hängt sich an den Anruf _phoneListener.ShutDown(), als ob ich den Ereignismechanismus nicht verwenden würde.

OK, also welche Teile der Magie vermisse ich, oder völlig falsch verstanden? Es scheint, dass alle Klempner, die ich erstellt habe, nicht viel getan haben, außer den Code zu überladen ...

Vielen Dank für alle reponses, Dave

0

Wenn Sie einen Event-Handler direkt von Thread A nennen, alle Handler für dieses Ereignis sind auch Thema A. ausgeführt Es gibt keine Möglichkeit, dass die Handler in Ihre anderen zu injizieren Faden. Sie könnte einen anderen Thread spawnen, um Shutdown auszuführen, aber dies löst immer noch nicht das Problem von Initialize noch nicht fertig ist - es verschiebt nur das Problem noch einmal. Sie können einen wartenden Dialog für den Benutzer anzeigen.

Letztendlich, wenn diese Codezeile hängt für eine Weile:

_phoneListener.Initialize(_strAddress); 

dann gibt es nichts, was man wirklich abgesehen von Warte tun kann, dass (entweder auf dem UI-Thread zu beenden oder in einem anderen Thread mit irgendeiner Form von Waiting UI) und nach diesem Aufruf eine Prüfung durchführen, die zum Beenden des Handler-Threads führt.

_phoneListener.Initialize(_strAddress); 
if (_exit.WaitOne(0)) 
{ 
    return; 
} 

In Ihrer Haupt-Shutdown-Routine wollen Sie so etwas wie:

_exit.Set(); 
_th.Join(); 

// Rest of cleanup code 

Dies wird dann warten, bis der Faden zu verlassen. Ich würde nicht empfehlen, Abort jemals auf einen Thread zu setzen, da Sie keine Ahnung haben, was mit einem Zustand passiert, auf den der Thread verwiesen hat. Wenn Sie einen Thread abbrechen müssen, dann planen Sie den Neustart der gesamten Anwendung als sicher.

+0

Danke, Simon. Das erklärt, warum das, was ich versucht habe, nicht funktioniert. Ich glaube jedoch, dass jeder, der antwortet, die falsche Frage beantwortet. Lassen Sie es mich erneut versuchen - vergessen Sie den Aufruf von Initialize(). Wenn ich den API-Code in einem Thread starte, kann ich ihn nicht stoppen. Der Aufruf von _listener.Shutdown() hängt, wodurch ich denke, dass der Thread, der zum Starten der Schnittstelle verwendet wurde, noch immer ausgeführt wird und auf Ereignisse von der API wartet. Wie kann ich den Thread stoppen? Es hat keine Schleifen, kann kein Flag prüfen, hat keinen Ort, um WaitOne() aufzurufen. Ich brauche einen Weg, um dem Thread zu signalisieren, sich selbst abzuschalten. Ideen? – DaveN59

+0

BTW, der Aufruf von Initialize wird schließlich zurückkehren, aber es kann eine Angelegenheit von Minuten statt von Sekunden sein. Es ist jedoch in Ordnung, da der Rest des Programms bei Bedarf ohne den Listener ausgeführt werden kann. – DaveN59

+1

Leider ist es ohne Zugriff auf den Code des Hörers sehr schwer zu sagen, warum das so ist. Das erste, was ich tun würde, ist den Debugger anzuhalten, während er in Shutdown() hängt. Sehen Sie sich nun an, was es zu tun versucht und welche Threads aktiv sind. Fügen Sie am Ende Ihrer Thread-Funktion eine Ablaufverfolgung hinzu, um herauszufinden, ob sie tatsächlich bis zum Ende geht und beendet wird. Sie können beweisen, dass Ihr Thread nicht mehr existiert, indem Sie Join() aufrufen. Wenn Join() zurückkehrt, wird Ihr Thread nicht mehr ausgeführt. –