[RESOLVED] Delegate, Invoke, Threads and anormal bug
CodeGuru Home VC++ / MFC / C++ .NET / C# Visual Basic VB Forums Developer.com
Results 1 to 5 of 5

Thread: [RESOLVED] Delegate, Invoke, Threads and anormal bug

  1. #1
    Join Date
    Feb 2012
    Location
    Strasbourg, France
    Posts
    116

    [RESOLVED] Delegate, Invoke, Threads and anormal bug

    Hello everybody,

    I'm doing a server with a form and to update the "console" I use delegates as my server class is different from the form class (have to be imported later). This function is called UpdateLog(String mymessage)

    In the whole program I can call it as many times i want, no problem.
    During the closing we have a big problem : the program frezzes on an update log. Here is a part of the code (I removed sensitives/useless parts)

    Code:
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Windows.Forms;
    using System.Net;
    using System.Threading;
    using System.Security.Cryptography;
    using System.Net.Sockets;
    using System.IO;
    
    namespace Serveur
    {
        public partial class FauxServeur : Form
        {
            // Variables d'état
            public bool bStartEnable = true;
    
            #region Update Log
            public delegate void DeleguePourUpdateLog(String message);
    
            private void UpdateLog(String s)
            {
                textBoxLog.AppendText(s);
            }
    
            public void StatusChanged(object sender, EventPourUpdateLog e)
            {
                this.Invoke(new DeleguePourUpdateLog(this.UpdateLog), new object[] { e.message });
                
            }
            #endregion
    
            public FauxServeur()
            {
                InitializeComponent();
                Serveur.StatusChanged += new StatusChangedEventHandler(StatusChanged);
            }
    
            private void buttonStart_Click(object sender, EventArgs e)
            {
                if (bStartEnable)
                {
                    textBoxIp.Enabled = false;
                    textBoxPort.Enabled = false;
                    if (Serveur.StartServer(textBoxIp.Text, int.Parse(textBoxPort.Text)))
                    {
                        bStartEnable = false;
                        buttonStart.Text = "Déconnecter";
                    }
                    else
                    {
                        textBoxIp.Enabled = true;
                        textBoxPort.Enabled = true;
                    }
                }
                else
                {
                    Serveur.Close();
                    textBoxIp.Enabled = true;
                    textBoxPort.Enabled = true;
                    bStartEnable = true;
                    buttonStart.Text = "Démarrer";
                }
            }
    
        }
    
        #region Délégué et classe evènement
    
        public delegate void StatusChangedEventHandler(object sender, EventPourUpdateLog e);
    
        public class EventPourUpdateLog : EventArgs
        {
            private String myMessage;
            public String message
            {
                get
                {
                    return myMessage;
                }
                set
                {
                    myMessage = value;
                }
            }
    
            public EventPourUpdateLog(String _mess)
            {
                myMessage = _mess;
            }
        }
    
        #endregion
        
        static class Serveur
        {
            // Variables pour l'update du log
            private static EventPourUpdateLog e;
            public static event StatusChangedEventHandler StatusChanged;
            /// <summary>
            /// ATTENTION : Cette fonction ne rajoute pas le Environment.NewLine à la fin
            /// </summary>
            /// <param name="Message"></param>
            public static void UpdateLog(String Message)
            {
                e = new EventPourUpdateLog(Message);
                OnStatusChanged(e);
            }
            private static void OnStatusChanged(EventPourUpdateLog e)
            {
    
                Delegate d = StatusChanged.GetInvocationList()[0];
                d.DynamicInvoke(new object[] { null, e });
    
                return;
                
                StatusChangedEventHandler statusHandler = StatusChanged;
                if (statusHandler != null)
                {
                    // Invoque le délégué
                    statusHandler(null, e);
                     //statusHandler.DynamicInvoke(null, e);
                }
            }
    
            // Infos sur le serveur
            private static IPAddress myIp;
            private static int myPort;
            private static IPEndPoint myEndPoint;
            private static TcpListener myListener;
            private static Thread myThreadListener;
            private static Thread myThreadManager;
            private static List<Client> myListClient;
    
            // Cryptage de l'envoi des clés
            private static DESCryptoServiceProvider desKeysExchange;
            public static ICryptoTransform encryptor;
    
            // Variables d'état
            private static bool bListenerEnable = false;
            private static bool bServerEnable = false;
            private static bool bShutdownInitiated = false;
            private static ManualResetEvent ConnexionAcceptee = new ManualResetEvent(false);
            private static ManualResetEvent FermetureComplete = new ManualResetEvent(false);
            private static ManualResetEvent FermetureListener = new ManualResetEvent(false);
    
            public static bool StartServer(String ip, int port)
            {
                UpdateLog("[INFO] Vérification des paramètres." + Environment.NewLine);
                bool bOk = true;
                if (!IPAddress.TryParse(ip, out myIp))
                {
                    bOk = false;
                    UpdateLog("[ERREUR] Adresse IP incorrecte." + Environment.NewLine);
                }
                if (port > 65535 || port < 1)
                {
                    bOk = false;
                    UpdateLog("[ERREUR] Port incorrect. Le port doit être compris entre 1 et 65534." + Environment.NewLine);
                }
                if (bOk)
                {
                    myListClient = new List<Client>();
                    myIp = IPAddress.Parse(ip);
                    myPort = port;
                    myEndPoint = new IPEndPoint(myIp, myPort);
    
    
                    UpdateLog("[INFO] Préparation du crypteur" + Environment.NewLine);
                    desKeysExchange = new DESCryptoServiceProvider();
                    desKeysExchange.Key = bytesDesKeyExchangeKey;
                    desKeysExchange.IV = bytesDesKeyExchangeIV;
                    encryptor = desKeysExchange.CreateEncryptor();
    
    
    
                    UpdateLog("[INFO] Vérification terminée. Connexion à : " + myEndPoint.ToString() + Environment.NewLine);
                    UpdateLog("[INFO] Ouverture du port d'écoute." + Environment.NewLine);
                    myListener = new TcpListener(myEndPoint);
                    bServerEnable = true;
                    bListenerEnable = true;
                    myThreadManager = new Thread(new ThreadStart(ManagerLoop));
                    myThreadManager.Start();
                    myThreadListener = new Thread(new ThreadStart(ListenerLoop));
                    myThreadListener.Start();
                    return true;
                }
                else
                {
                    return false;
                }
            }
    
            private static void ListenerLoop()
            {
                try
                {
                    UpdateLog("[INFO] Démarrage du serveur" + Environment.NewLine);
                    myListener.Start();
                    while (bListenerEnable)
                    {
                        //UpdateLog("[INFO] En attente de clients" + Environment.NewLine);
                        if (myListener.Pending())
                        {
                            //ConnexionAcceptee.Reset();
                            TcpClient tc = myListener.AcceptTcpClient();
                            Client c = new Client(tc);
                            UpdateLog("[INFO] Nouveau client" + Environment.NewLine);
                            myListClient.Add(c);
                            //ConnexionAcceptee.WaitOne();
                        }
                    }
                    UpdateLog("[INFO] Fermeture listener" + Environment.NewLine);     
                    Console.WriteLine("1");
                    myListener.Stop();
                    Console.WriteLine("2");
                    UpdateLog("[INFO] Listener fermé" + Environment.NewLine);    //here it crashes, when this line is commented everything is fine, else it crashes randoly
                    Console.WriteLine("3");
                    //FermetureListener.Set();
                }
                catch (ThreadAbortException)
                {
                    UpdateLog("[INFO] Abort sur listener reçu" + Environment.NewLine);
                    myListener.Stop();
                    //UpdateLog("[INFO] Fermeture listener sur Abort request" + Environment.NewLine);
                    //FermetureListener.Set();
                }
            }
    
            private static void ManagerLoop()
            {
                UpdateLog("[INFO] Démarrage du manager" + Environment.NewLine);
                while (bServerEnable)
                {
                    List<Client> DeletionList = new List<Client>();
                    if (myListClient.Count != 0)
                    {
                        foreach (Client c in myListClient)
                        {
                            if (c.bMarkedForDeletion || bShutdownInitiated)
                            {
                                c.Close();
                                DeletionList.Add(c);
                            }
                        }
                    }
                    if (DeletionList.Count != 0)
                    {
                        foreach (Client c in DeletionList)
                        {
                            myListClient.Remove(c);
                        }
                    }
                    if (!bShutdownInitiated)
                    {
                        Thread.Sleep(Constants.ManagerThreadPauseTime);
                    }
                    else
                    {
                        bServerEnable = false;
                        //FermetureComplete.Set();
                    }
                }
            }
    
            public static void Send(String message)
            {
                UpdateLog("[INFO] Envoi du message suivant : " + Environment.NewLine);
                UpdateLog(message + Environment.NewLine);
                if (myListClient != null)
                {
                    if (myListClient.Count != 0)
                    {
                        foreach (Client c in myListClient)
                        {
                            c.SendData(message);
                        }
                    }
                    else
                    {
                        UpdateLog("[AVERTISSEMENT] Aucun client connecté, envoi du message annulée" + Environment.NewLine);
                    }
                }
                else
                {
                    UpdateLog("[ERROR] Le serveur n'est pas initialisé" + Environment.NewLine);
                }
            }
    
            public static void Close()
            {
                FermetureComplete.Reset();
                FermetureListener.Reset();
                UpdateLog("[INFO] Shutdown in progress" + Environment.NewLine);
                // C'est le manager qui va fermer tout le monde
                bListenerEnable = false;
                //ConnexionAcceptee.Set();
                bShutdownInitiated = true;
                //FermetureComplete.WaitOne();
                //FermetureListener.WaitOne();
    
                //myThreadListener.Abort();
                if (myThreadListener.IsAlive)
                {
                    //UpdateLog("[INFO] Attente fermeture listener" + Environment.NewLine);
                    myThreadListener.Join();
                }
    
                // Normalement le manager est déjà fermé !
                myThreadManager.Abort();
                if (myThreadManager.IsAlive)
                {
                    UpdateLog("[INFO] Attente fermeture manager" + Environment.NewLine);
                    myThreadManager.Join();
                }
    
                UpdateLog("[INFO] Fermeture terminée" + Environment.NewLine);
            }
    
        }
    
    }
    Problematic section is in blue

    The outputs is as fellow : The thread '<No Name>' (0x1bec) has exited with code 0 (0x0).
    1
    2

    The button goes through 2 states : start, disconnect

    1) Start : OK
    2) Disconnect : 50 % chance freeze
    3) (if it didn't freeze) Start : OK
    4) Disconnect : 100% chance freeze

  2. #2
    Join Date
    Jan 2010
    Posts
    1,099

    Re: Delegate, Invoke, Threads and anormal bug

    I'm not sure, but, let's see if the problem can be systematically isolated.

    This is what is happening (I'll use --> for "calls"):
    Serveur.UpdateLog(string) --> Serveur.OnStatusChanged(...) --> /* via event */ -->
    FauxServeur.StatusChanged(...) --> FauxServeur.UpdateLog(string) --> textBoxLog.AppendText(string)


    Now, go backwards up the chain, and comment out each call until you isolate the problem. For example, the class of textBoxLog (dunno what it is since you didn't show it) could be the source of the problem. If not, then go one level up, and comment out the call to UpdateLog(...) in the FauxServeur class. (If it's an event handler, you can just return at the start.)
    Repeat until the bug disappears - once it does, you've narrowed the list of suspects to a single method. Then come hear and tell us what you found out, maybe someone will be able to help.

    You can also put a breakpoint at first the UpdateLog() call in Serveur class, and then observe what happens by going step-by-step (F11 - "Step Into").

    It probably has something to do with threading - but not directly. Maybe the event is trying to invoke the handler on an invalid object. Is there an exception? If so, of what kind? Or it just blocks? What was the expected output?
    The message "The thread '<No Name>' (0x1bec) has exited with code 0" doesn't tell us much, as traditionally, exit code 0 signifies successful completion...

    P.S. Why are you using DynamicInvoke?

  3. #3
    Join Date
    Feb 2012
    Location
    Strasbourg, France
    Posts
    116

    Re: Delegate, Invoke, Threads and anormal bug

    Oppps DynamicInvoke was a test to see if it would work better so here's the original version :
    Code:
            private static void OnStatusChanged(EventPourUpdateLog e)
            {
                StatusChangedEventHandler statusHandler = StatusChanged;
                if (statusHandler != null)
                {
                    // Invoque le d&#233;l&#233;gu&#233;
                    statusHandler(null, e);
                     //statusHandler.DynamicInvoke(null, e);
                }
            }
    textBoxLog is err.... a textBox


    No exception thrown. Breakpoint and stepinto just go in freeze when reaching UpdateLog


    Ok let's try your method, here's the piece of code failing let's see how commenting changes it.

    Code:
    1.                Console.WriteLine("1");
    2.                UpdateLog("[INFO] Fermeture listener" + Environment.NewLine);
    3.                Console.WriteLine("2");
    4.                myListener.Stop();
    5.                Console.WriteLine("3");
    6.                UpdateLog("[INFO] Listener ferm&#233;" + Environment.NewLine);
    7.                Console.WriteLine("4");
    8.                FermetureListener.Set();
    9.                Console.WriteLine("5");
    I'll follow your commeting scenario : Serveur.UpdateLog(string) --> Serveur.OnStatusChanged(...) --> /* via event */ --> FauxServeur.StatusChanged(...) --> FauxServeur.UpdateLog(string) --> textBoxLog.AppendText(string)

    Of course I uncomment the previous each time.


    1st case : nothing commented

    Last line executed was line 5


    2nd case : commenting line 6

    Complete execution. Line 9 was executed


    3rd case : commenting Serveur.OnStatusChanged(...) inside the Serveur.UpdateLog function

    Complete execution. Line 9 was executed


    4th case : commenting statusHandler(null, e); inside Serveur.OnStatusChanged

    Complete execution. Line 9 was executed


    5th case : commenting this.Invoke(new DeleguePourUpdateLog(this.UpdateLog), new object[] { e.message }); inside FauxServeur.StatusChanged()

    Complete execution. Line 9 was executed


    6th case : commenting textBoxLog.AppendText(s); inside FauxServeur.UpdateLog()

    Line 3 never reached going further inside to find where it stopped

    This is Serveur.UpdateLog()
    Code:
            public static void UpdateLog(String Message)
            {
                Console.WriteLine("21");
                e = new EventPourUpdateLog(Message);
                Console.WriteLine("22");
                OnStatusChanged(e);
                Console.WriteLine("23");
            }
    Console.WriteLine("23"); is never reached. Going further inside.



    This is Serveur.OnStatusChanged
    Code:
            private static void OnStatusChanged(EventPourUpdateLog e)
            {
                Console.WriteLine("30");
                StatusChangedEventHandler statusHandler = StatusChanged;
                Console.WriteLine("31");
                if (statusHandler != null)
                {
                    Console.WriteLine("32");
                    // Invoque le d&#233;l&#233;gu&#233;
                    statusHandler(null, e);
                    Console.WriteLine("33");
                     //statusHandler.DynamicInvoke(null, e);
                }
            }
    Console.WriteLine("33"); is never reached. Going further inside.

    This is FauxServeur.StatusChanged()
    Code:
            public void StatusChanged(object sender, EventPourUpdateLog e)
            {
                Console.WriteLine("40");
                this.Invoke(new DeleguePourUpdateLog(this.UpdateLog), new object[] { e.message });
                Console.WriteLine("41");
            }
    Console.WriteLine("41"); is never reached. Going further inside.

    Here is in this case the code for FauxServeur.UpdateLog()
    Code:
            private void UpdateLog(String s)
            {
                Console.WriteLine("50");
                //textBoxLog.AppendText(s);
                Console.WriteLine("51");
            }
    Console.WriteLine("50"); is never reached canno't go further :/


    Any idea ? for the moment the programs runs by being executed in case n&#176;2

    Thanks !
    Last edited by Erendar; April 3rd, 2012 at 02:19 AM. Reason: typo

  4. #4
    Join Date
    Jan 2010
    Posts
    1,099

    Re: Delegate, Invoke, Threads and anormal bug

    Not sure, but I'll give it a shot.
    The fact that it blocks, rather than throwing an exception, is indicative of a deadlock.
    In your case, it seems to be a particularly tricky bug...

    And it's not that easy to reproduce.
    I tried to reproduce the problem by writing a small test app with a similar setup, but at first it didn't block for me - I simply got an InvalidOperation exception with the message "Invoke or BeginInvoke cannot be called on a control until the window handle has been created."

    This message is slightly misleading, because in this case, the underlying native window handle was created, but then destroyed since the form is closing. Basically, the "worker" thread is trying to call Invoke() on an invalid object.
    Have you encountered this exception during development, and then tried to fix it by using some form of synchronization? I can see that you're using ManualResetEvent-based synchronization in your Serveur class, but I didn't really got a detailed look, will do later if I find time. Also, are you doing anything special in your FauxServeur form, when it comes to closing (like, overriding OnFormClosing(), or something along those lines)?
    I've experimented a bit, using the lock keyword to mark 2 critical sections at each thread (UI and "worker"); although this may appear to work, it will occasionally lead to a deadlock when the form starts closing.

    This is why, in a nutshell: the Control.Invoke() method (from the "worker" thread) basically posts a Windows message to be processed later by the internal message loop running on the UI thread, and then waits for the callback method to complete on the UI thread (blocking the "worker thread"). However, if the lock on the UI thread happens to be still in effect, the UI thread is blocked as well, and thus the callback never completes, and Invoke never returns...

    IMO, it is something similar to, but apparently not exactly the same as, this bug. Except in your case, the root at the problem is related to closing the form.

    Now, there could be other stuff going on in there - you might try to inspect what's going on inside your application by first by causing it to hang, and then attaching the debugger to it (Debug > Attach to process...), and hitting Break All. Then check the call stack. You might also want to display the Threads window, so that you can switch debugging from one thread to the other (Debug > Windows > Threads). Are there WaitForWaitHandle(), and WaitHandle.WaitOne() calls in the call stack? Does it say that one or both threads are blocked? If you press Continue and then Break All again, does something change? (I'm asking because the WaitForWaitHandle() method, under certain conditions, might be simply stuck in a loop relying on waitHandle.WaitOne(1000, false) returning true in order to exit the loop - it's an internal implementation detail, but this is the part that might be blocking the Invoke method from returning).

    Anyway, I'm not sure if this will help you, but the bottom line is: the UI thread must stay responsive. So, you could try a workaround. Provide a way for the FauxServeur class to check from the UI thread if the "worker" thread (listener thread in your case) is finished (you can use Thread.IsAlive). Also, provide a way to signal the "worker" thread(s) that that the while loop should end (a bit more on that later). Override OnFormClosing to check if the "worker" is still running, and if so, cancel the the closing operation by setting e.Cancel = true, but also set a bool flag to indicate that a close request has occurred, and also signal the other thread(s) to stop. Then call base.OnFormClosing(e) unconditionally.

    A word about Boolean flags: bools can be set atomically; if a bool field is additionally marked with the volatile keyword, the compiler is given a hint that it will be used by multiple threads, so it will disable some optimizations that might get in the way, effectively enabling you to safely access the bool flag from multiple threads without using standard synchronization methods (more info here). You can use this to both stop your "worker" thread and handle the closing of the form.

    Something like this:
    Code:
            private volatile bool m_shouldClose = false;
            protected override void OnFormClosing(FormClosingEventArgs e)
            {
                if (workerThread.IsAlive)
                {
                    // wait for the other threads to finish before actually closing
                    m_shouldClose = true;
                    e.Cancel = true;   
    
                    // simply sets another volatile bool
                    // eventually causing the loops to exit
                    Serveur.Stop = true;   
                }
    
                base.OnFormClosing(e);
            }
    Then, you could wrap the call to Invoke in a conditional - not really required, but it will prevent updating in the time between the close request and the form actually closing:
    Code:
    if (!m_shouldClose)
    {
        this.Invoke(new DeleguePourUpdateLog(this.UpdateLog), new object[] { e.message });
    }
    Finally, and this is a bit ugly/hackish, you check if the form should be closed in Application.Idle event handler, that you can add in the constructor of FauxServeur, like this:
    Code:
    // the delegate used here simply enables me to avoid writing a 
    // separate method for such a small chunk of code
    Application.Idle += delegate 
    {
        if (m_shouldClose)
            Close();
    };
    This will then simply cause OnFormClosing() to be called, which will again check if the non-UI threads are finished, and if so, close the app.

    HTH.

    P.S. Something that might look as a solution: setting the IsBackground properties of the non-UI threads to true will cause them to stop abruptly when the main thread exits (more precisely, when all foreground threads are done, but your main thread is the UI thread, right?). This may appear to work, but I'm not sure I'd recommend going down that road, as I suspect there could be potential pitfalls there. Any comments from someone more experienced on background threads?

    P.P.S.
    textBoxLog is err.... a textBox
    If it weren't for you, I would have probably never find out that the TextBox class has an AppendText() method.
    Never used it. LOL
    That's why I asked - I assumed it's some custom class or something.
    Last edited by TheGreatCthulhu; April 3rd, 2012 at 06:48 PM.

  5. #5
    Join Date
    Feb 2012
    Location
    Strasbourg, France
    Posts
    116

    Re: Delegate, Invoke, Threads and anormal bug

    *victory fanfare from final fantasy*

    **** you won ! Just nammed my threads better and check with the Debug->Windows->Threads (didn't knew that feature and it allowed me to understand immediatly) and found that I'm in a deadlock

    Serveur.Close() is called by GUI, but hangs in myThreadListener.Join();

    And the listener thread is blocked on this.Invoke(new DeleguePourUpdateLog(this.UpdateLog), new object[] { e.message });
    meaning it waits for the GUI to process data but the GUI is already waiting.

    This is pure school deadlock case :/

    I cheated by creating a closing thread that'll manage all closing without freezing the GUI

    Code:
            public static void Close(bool _bClose)
            {
                bClose = _bClose;
                myThreadClosing = new Thread(new ThreadStart(CloseProcessing));
                myThreadClosing.Start();
            }
    private static void CloseProcessing()
            {
                Thread.CurrentThread.Name = "CloseProcessing";
                UpdateLog("[INFO] Shutdown in progress" + Environment.NewLine);
                // C'est le manager qui va fermer tout le monde
                bListenerEnable = false;
                bManagerEnable = false;
    
                if (myThreadListener != null)
                {
                    if (myThreadListener.IsAlive)
                    {
                        UpdateLog("[INFO] Attente fermeture listener" + Environment.NewLine);
                        myThreadListener.Join();
                    }
                }
    
                // Normalement le manager est déjà fermé !
                if (myThreadManager != null)
                {
                    if (myThreadManager.IsAlive)
                    {
                        UpdateLog("[INFO] Attente fermeture manager" + Environment.NewLine);
                        myThreadManager.Join();
                    }
                }
    
                UpdateLog("[INFO] Fermeture terminée" + Environment.NewLine);
                if (bClose)
                {
                    Environment.Exit(0);
                }
            }
    I know it's not perfect but this way I keep control of the GUI

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  


Azure Activities Information Page

Windows Mobile Development Center


Click Here to Expand Forum to Full Width

This is a CodeGuru survey question.


Featured


HTML5 Development Center