Laden...

Application wird nicht komplett beendet

Erstellt von bluedragon vor 14 Jahren Letzter Beitrag vor 14 Jahren 2.536 Views
B
bluedragon Themenstarter:in
101 Beiträge seit 2008
vor 14 Jahren
Application wird nicht komplett beendet

Hallo liebe myCSharp'ler,
ich habe mal wieder nen kleines Problem, was mich aber doch nervt und ich es alleine nicht behoben bekomme.

Es sieht wie folgt aus:
Ich schreibe zur Zeit an einem Chat mit Server und Client. Der Server horcht über einen ausgelagerten Thread mit den Klassen TcpListener & TcpClient nach eventuellen Verbindungsanfragen.
Nun ist das Problem, dass ich in dem Thread ne Endlosschleife habe, um nach einer eingegangenen Verbingungsaufforderung ein paar Dinge zu regeln und dann wieder erneut zu warten, und meine Application nichtmehr komplett beendet wird sobald ich auf das X oben rechts klicke oder auf meinen "Beenden"-Button.
Ich habe es auch schon mit Thread.Abort() oder Thread.Interrupt() probiert, damit er nichtmehr horcht und alles beendet wird, aber das klappt nichtmehr 😦

Hier mal die wichtigen Segmente:


    

//das hier ist die Methode für den Auslagerungsthread  
private void clientsHorchen()
{
    TcpListener listener = new TcpListener(60000);
    listener.Start();
    int chatterZähler = 0;

    while(true)
    {
        TcpClient client = listener.AcceptTcpClient();

        //noch ein paar Dinge machen, die 
        //aber auf keine Fall den Thread aufhalten        
    }
}  

///////////---------------------

//das hier ist der "Beenden"-Button
private void beenden_Click(object sender, EventArgs e)
{
     host.Abort();
     Application.ExitThread(); //auch bei Application.Exit();
}

Ich hoffe die Angaben reichen. Auf Jedenfall passiert es nicht, dass die Application nur halb beendet wird , wenn ich die Endlosschleife rausnehme.
Halb beenden heißt soviel, dass die Form geschlossen wird, jedoch der Debugger vom MS Visual Studio mir sagt, dass die Application noch nicht beendet ist.

MfG
bluedragon

Man muss viel gelernt haben, um nach etwas, worüber man nicht Bescheid weiß, richtig fragen zu können.

Wenn du jemandem vertrauen kannst, erübrigt sich ein Vertrag. Kannst du ihm nicht vertrauen, ist ein Vertrag nutzlos.

1.820 Beiträge seit 2005
vor 14 Jahren

Hallo!

Dann mach doch mal bei Application.ExitThread() einen Breakpoint und schau im Studio-Debugger mal die Liste der laufenden Threads an, ob der Thread wirklich beendet wurde.

Nobody is perfect. I'm sad, i'm not nobody 🙁

B
bluedragon Themenstarter:in
101 Beiträge seit 2008
vor 14 Jahren

Wo schaue ich mir die laufenden Threads während des debuggens an ?
Finde diese Option im MSV Studio nicht 😦

MfG
bluedragon

Man muss viel gelernt haben, um nach etwas, worüber man nicht Bescheid weiß, richtig fragen zu können.

Wenn du jemandem vertrauen kannst, erübrigt sich ein Vertrag. Kannst du ihm nicht vertrauen, ist ein Vertrag nutzlos.

B
bluedragon Themenstarter:in
101 Beiträge seit 2008
vor 14 Jahren

Sorry für den Doppelpost, aber ich weiß immernoch nicht was ich dagegen tuen kann 😦

Und wie gesagt, ich find die Option im MSV Studio nicht.

MfG
bluedragon

Man muss viel gelernt haben, um nach etwas, worüber man nicht Bescheid weiß, richtig fragen zu können.

Wenn du jemandem vertrauen kannst, erübrigt sich ein Vertrag. Kannst du ihm nicht vertrauen, ist ein Vertrag nutzlos.

3.971 Beiträge seit 2006
vor 14 Jahren

Es gibt 2 Möglichkeiten das Problem zu beheben.

  1. Ich nehme an du erstellst selbst einen Thread (verwendest also keinen aus ThreadPool), dann setze die Thread.IsBackground-Eigenschaft auf true, bin mir aber nicht ganz sicher ob das funktioniert, warum siehe unten.
  2. Verwende statt AcceptTcpClient/AcceptSocket die asynchrone Variante TcpListener.BeginAcceptXYZ. Das ganze nennt sich asynchrones Entwurfsmuster und kannst du dir gerne asugiebig auf der MSDN anschauen: Entwurfsmuster für die asynchrone Programmierung

Die 3) Möglichkeit wäre, du erstellst beim Beenden deines Programms einen neuen TcpClient und verbindest dich mit deinem TcpListener (der im selben Programm werkelt), ist absolut nicht schön funktioniert aber.

Der Grund für das Verhalten, die nicht asynchronen Funktionen (in diesem Fall AcceptXYZ) halten den aktuellen Thread solange an, bis sich ein neuer Client mit dem Listener verbunden hat. Thread.Abort hingegen wirkt nicht, da sich der Thread, der AcceptXYZ aufgerufen hat in unmanaged Code befindet. Erst wenn der Thread wieder von unmanaged zu managed Code übergeht, wirft die CLR eine ThreadAbortException.

Es gibt 3 Arten von Menschen, die die bis 3 zählen können und die, die es nicht können...

B
bluedragon Themenstarter:in
101 Beiträge seit 2008
vor 14 Jahren

Danke sowas wollte ich doch hören 😃)

Asynchrone Verbindungen kenne ich, aber mag ich nicht so Recht. Ich möchte genau wissen, was wann und wo in meinem Code passiert und nicht "könnte" 😉
Aber werde mir mal die anderen Sachen anschauen.

MfG
bluedragon

Man muss viel gelernt haben, um nach etwas, worüber man nicht Bescheid weiß, richtig fragen zu können.

Wenn du jemandem vertrauen kannst, erübrigt sich ein Vertrag. Kannst du ihm nicht vertrauen, ist ein Vertrag nutzlos.

3.971 Beiträge seit 2006
vor 14 Jahren

Asynchrone Verbindungen kenne ich, aber mag ich nicht so Recht. Ich möchte genau wissen, was wann und wo in meinem Code passiert und nicht "könnte" 😉

Kann ich nicht nachvollziehen. Ziel der asynchronen Programmierung ist es, nicht auf ein Ereignis (sinnlos) zu warten, sondern entsprechend informiert zu werden. Weiterhin lassen sich mit dem asynchronen Entwurfsmuster leicht und effektiv die last auf mehreren Prozessoren(/-Kernen) verteilen.

Es gibt 3 Arten von Menschen, die die bis 3 zählen können und die, die es nicht können...

B
bluedragon Themenstarter:in
101 Beiträge seit 2008
vor 14 Jahren

Ich glaube dann hatte ich den Sinn von asychronen Programmiertechniken nicht richtig verstanden. Ich habe mir das nur so vorgestellt, dass ich irgendwo den entsprechenden Code hinpacke und dann danach der nächste Code weiterverarbeitet wird in der CRL während im Hintergrund irgendwann ein Ereignis ausgestoßen wird, wodurch dann eine Methode aufgerufen wird. Mein Problem war dann immer (oder ist) - gedanklich, dass der Code, der dann weiterläuft, nichts mehr mit dem zu tuen hat was die Methode des Event-Handlers von dem ausgestoßenen Ereignis macht.

MfG
bluedragon

Man muss viel gelernt haben, um nach etwas, worüber man nicht Bescheid weiß, richtig fragen zu können.

Wenn du jemandem vertrauen kannst, erübrigt sich ein Vertrag. Kannst du ihm nicht vertrauen, ist ein Vertrag nutzlos.

49.485 Beiträge seit 2005
vor 14 Jahren

Hallo bluedragon,

Mein Problem war dann immer (oder ist) - gedanklich, dass der Code, der dann weiterläuft, nichts mehr mit dem zu tuen hat was die Methode des Event-Handlers von dem ausgestoßenen Ereignis macht.

tja, bei asynchroner Programmierung läuft tatsächlich Code parallel. Und das ist gedanklich in der Tat nicht einfach. Aber da muss man durch. Mehr als diese allgemeine Bemerkung kann ich allerdings auf eine so allgemein Anmerkung von dir nicht sagen. 😃

herbivore

3.971 Beiträge seit 2006
vor 14 Jahren

Einziges Problem bei der asynch-Programmierung ist das Verständnis, das der Code nicht mehr in einer Funktion enthalten ist, sondern über mehrere Funktionen verteilt werden muss.

Dieses Problem lässt sich beispielsweise sehr gut mit einer kleinen Hilfsklasse lösen, Beispiel:


internal class SocketAccepterTaskEventArgs {
  public TcpClient Client { get; }
}

internal class SocketAccepterTask {
  //Some Arguments/Properties
  public TcpListener Listener { get; set; }

  protected bool Aborting { get; }
  protected bool IsRunning { get; set; }

  private object m_lockobj = new Object();

  public event EventHandler<SocketAccepterTaskEventArgs> TcpClientAccepted;

  public void Begin() {
    lock (m_lockobj) {
      if (IsRunning) throw ...;
      if (Listener == null) throw ...;
    
      AcceptTcpClient();

      IsRunning = true;
    }
  }

  private IAsyncResult m_lastResult;
  private void AcceptTcpClient() {
     lock (m_lockobj) {
       m_lastResult = Listener.BeginAcceptTcpClient(
     }
  }

  private void Listener_AcceptTcpClientCallback(object state) {
    lock (m_lockobj) {
      if (!Aborting) {
        TcpClient result = Listener.EndAcceptTcpClient(m_lastResult);
        OnTcpClientAccepted(result);

        AcceptTcpClient();
      }
    }
  }

  protected void OnTcpClientAccepted(TcpClient client) {
    EventHandler<SocketAccepterTaskEventArgs> handler = TcpClientAccepted;
    if (handler != null) handler(this, new SocketAccepterTaskEventArgs(client));
  }

  public void Abort() {
    lock (m_lockobj) {
      if (!Aborting) Aborting = true;
    }
  }
}

Verwendung:


public class MyServerApp {
  private void Init() {
    TcpListener listener = ...;
    SocketAccepterTask task = new SocketAccepterTask();
    task.Listener = listener;
    task.TcpClientAccepted+= NewTcpClient_Accepted;
    task.Begin();
  }

  private void NewTcpClient_Accepted(object sender, SocketAccepterTaskEventArgs e) {
    e.TcpClient.DoSomething();
  }
}

Hauptvorteil der Klasse SocketAccepterTask ist, dass sich das ganze sehr einfach skalieren lässt. Einfach eine zusätzliche Instanz erstellen und fertig. Weiterhin lässt sich durch den Einsatz des Events TcpClientAccepted mehrere Tasks miteinander verbinden. Nachteil des ganzen, mehr Code.

PS: In diesem Fall muss man EndXYZ aufrufen, da dort der eigentliche Rückgabewert enthalten ist. Bei anderen Funktionen ohne Rückgabewert muss aber auch irgendwo/irgendwann EndXYZ aufgerufen werden, da sich in MS-Klassen gemerkt wird, wer BeginXYZ aufgerufen hat. Das könnte bei größeren Anwendungen zu einem Speicherproblem führen.

Weiterhin ist hier das lock wichtig für die beiden Funktionen AcceptTcpClient und Listener_AcceptTcpClientCallback, da die Zuweisung auf m_lastResult später erfolgen könnte als der Aufruf der Callback-Funktion.

Es gibt 3 Arten von Menschen, die die bis 3 zählen können und die, die es nicht können...

B
bluedragon Themenstarter:in
101 Beiträge seit 2008
vor 14 Jahren

Danke für die Antworten !
Ich muss sagen , dass ich wirklich noch eine Menge lernen muss, denn mir wird nicht so richtig klar was die Klasse jetzt vorteilhaftes bringt (was möglicherweise auch daran liegt, dass einige Codefragmente für mich unbekannte Dinge enthalten). Zusätzlich erschüttert es mich ein wenig, dass ein für mich simples Problem durch so einen Umstand behandelt werden muss und dass C# keine Möglichkeit bietet einfach einen Thread zu stoppen.

Trotzdem danke ich dafür und ich werde mich mal an den asynchronen Optionen die C# so zu bieten hat üben.

MfG
bluedragon

Man muss viel gelernt haben, um nach etwas, worüber man nicht Bescheid weiß, richtig fragen zu können.

Wenn du jemandem vertrauen kannst, erübrigt sich ein Vertrag. Kannst du ihm nicht vertrauen, ist ein Vertrag nutzlos.

S
8.746 Beiträge seit 2005
vor 14 Jahren

dass C# keine Möglichkeit bietet einfach einen Thread zu stoppen.

Weil es kein triviales Pronlem ist. Threads kann die CLR nur dann stoppen, wenn auch .NET-Code ausgeführt wird. Deine Socket-Operationen führen aber irgendwann zu Betriebssystem-Aufrufen, und die sind durch .NET nicht unterbrechbar.

Zum anderen sind catch/finally Blöcke ein Problem. Um keine Ressourcen-Lecks entstehen zu lassen, dürfen diese nicht unterbrochen werden. Dann aber ist Thread.Abort nicht zuverlässig, denn was ist wenn z.B. im Finally ein Thread.Sleep(Timeout.Infinite) steht?

Der einzig sichere Weg ist also Threads selbst abbrechbar zu gestalten. Nur das ist zuverlässig. Thread.Abort kann man in bestimmten, einfachen Szenarien einsetzen (nur .NET-Code ohne blockierende BS-Aufrufe). Allerdings sollte man dann den Thread-Code selbst geschrieben haben um sicherzustellen, dass die o.g. Bedingungen nicht verletzt sind.

Das ist übrigens kein .NET-Problem, sondern das hast du auf allen Platformen die Threads anbieten (Java, etc.).

Zusätzlich erschüttert es mich ein wenig, dass ein für mich simples Problem durch so einen Umstand behandelt werden muss

Korrekterweise müßte der Satz heissen:

"Zusätzlich erschüttert es mich ein wenig, dass ich dachte, dass es sich bei Thread-Programmierung um ein simples Problem handelt."

😃

R
164 Beiträge seit 2008
vor 14 Jahren

Es reicht, den TcpListener mit TcpListener.Stop() anzuhalten. Wenn die AcceptTcpClient-Methode beim Stoppen ausgeführt wird, kommt es aber zu einer Exception, die man abfangen muss. Diese Diskussion gibt vielleicht weitere Anregungen: TcpClient/NetworkStream closing connections

B
bluedragon Themenstarter:in
101 Beiträge seit 2008
vor 14 Jahren

Danke für die weiteren Anworten !

Korrekterweise müßte der Satz heissen:

"Zusätzlich erschüttert es mich ein wenig, dass ich dachte, dass es sich bei Thread-Programmierung um ein simples Problem handelt."

😃

Hehe 😃
Aber ich würde es dann eher so formulieren: "Zusätzlich erschüttert es mich, dass Thread-Programmierung nicht so umgänglich ist."
Ich kann ja wohl kaum an meinen eigenen Verstand appellieren, wo käm ich denn dahin 😉

@rechner: Wenn ich ehrlich bin hat mir der Thread nicht wirklich weiter geholfen, weil es dort um was anderes geht. Ich habe ja kein Problem mit vermissten Packeten. Lediglich damit, dass ein Thread nicht beendet wird.

Habs nun aber nach der "unschönen" Methode gelöst, da es für mich wirklich das simpelste ist. Und warum schwer machen , wenns auch einfach geht. Ausserdem wüsste ich nicht so recht was daran sooo "unschön" sein soll, nur weil ich mich mit mir selber verbinde und damit die Blockade aufhebe, ist das doch kein "unschöner" Lösungsansatz. Zumindest ist das meine Meinung 😉



        #region Programm beenden

        private void button1_Click(object sender, EventArgs e)
        {  Close();   }

        private void form_closing(object sender, FormClosingEventArgs e)
        {
            programRunning = false;
            TcpClient TcpEnd = new TcpClient();
            TcpEnd.Connect(Dns.GetHostAddresses(Dns.GetHostName()), 60000);
            TcpEnd.Client.Disconnect(false);

            Application.Exit();
        }

        #endregion


programRunning ist die Variable die bei mir die Endloschleife für ausstehende Verbindungsaufforderungen steuert. Somit wird die Schleife beendet und der Thread auch.

Bin aber für weitere Anregungen gerne offen 😃

MfG
bluedragon

Man muss viel gelernt haben, um nach etwas, worüber man nicht Bescheid weiß, richtig fragen zu können.

Wenn du jemandem vertrauen kannst, erübrigt sich ein Vertrag. Kannst du ihm nicht vertrauen, ist ein Vertrag nutzlos.

3.971 Beiträge seit 2006
vor 14 Jahren

Zusätzlich erschüttert es mich, dass Thread-Programmierung nicht so umgänglich ist.

Dafür gibts für die "großen" Programmiersprachen diverse Frameworks, die dir den Umgang mit Threads erleichtern. Für .NET gibts beispielsweise die Microsoft Parallel Extensions to .NET Framework, ab .NET 4.0 direkt integriert. Allerdings sollte vorher das Verständnis dasein.

Es gibt 3 Arten von Menschen, die die bis 3 zählen können und die, die es nicht können...