locked
Rich TextBox ScrollToCaret Method Crashes -- Find Lines Marked Thus <-------------------- uncomment this line and watch the program crash RRS feed

  • Question

  • using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Windows.Forms;

    namespace Investigate_IGDM_in_Log_Viewer
    {
        static class Program
        {
            /// <summary>
            /// The main entry point for the application.
            /// </summary>
            [STAThread]
            static void Main()
            {
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
                Application.Run(new Form1());
            }
        }
    }
    ------
    namespace Investigate_IGDM_in_Log_Viewer
    {
        partial class Form1
        {
            /// <summary>
            /// Required designer variable.
            /// </summary>
            private System.ComponentModel.IContainer components = null;

            /// <summary>
            /// Clean up any resources being used.
            /// </summary>
            /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
            protected override void Dispose(bool disposing)
            {
                if (disposing && (components != null))
                {
                    components.Dispose();
                }
                base.Dispose(disposing);
            }

            #region Windows Form Designer generated code

            /// <summary>
            /// Required method for Designer support - do not modify
            /// the contents of this method with the code editor.
            /// </summary>
            private void InitializeComponent()
            {
                this.button1 = new System.Windows.Forms.Button();
                this.SuspendLayout();
                // 
                // button1
                // 
                this.button1.Location = new System.Drawing.Point(197, 12);
                this.button1.Name = "button1";
                this.button1.Size = new System.Drawing.Size(75, 23);
                this.button1.TabIndex = 0;
                this.button1.Text = "button1";
                this.button1.UseVisualStyleBackColor = true;
                this.button1.Click += new System.EventHandler(this.button1_Click);
                // 
                // Form1
                // 
                this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
                this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
                this.ClientSize = new System.Drawing.Size(284, 261);
                this.Controls.Add(this.button1);
                this.Name = "Form1";
                this.Text = "Form1";
                this.ResumeLayout(false);

            }

            #endregion

            private System.Windows.Forms.Button button1;
        }
    }
    ------
    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;

    namespace Investigate_IGDM_in_Log_Viewer
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
            }

            private void button1_Click(object sender, EventArgs e)
            {
                Int64 i;
                for (i = 0; ;i++ )
                    RegisterException.WriteLogEntry(i.ToString(), Color.Green);
            }
        }
    }
    ------
    namespace Investigate_IGDM_in_Log_Viewer
    {
        partial class LogViewer
        {
            /// <summary>
            /// Required designer variable.
            /// </summary>
            private System.ComponentModel.IContainer components = null;

            /// <summary>
            /// Clean up any resources being used.
            /// </summary>
            /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
            protected override void Dispose(bool disposing)
            {
                if (disposing && (components != null))
                {
                    components.Dispose();
                }
                base.Dispose(disposing);
            }

            #region Windows Form Designer generated code

            /// <summary>
            /// Required method for Designer support - do not modify
            /// the contents of this method with the code editor.
            /// </summary>
            private void InitializeComponent()
            {
                this.richTextBox1 = new System.Windows.Forms.RichTextBox();
                this.SuspendLayout();
                // 
                // richTextBox1
                // 
                this.richTextBox1.Location = new System.Drawing.Point(13, 13);
                this.richTextBox1.Name = "richTextBox1";
                this.richTextBox1.Size = new System.Drawing.Size(246, 236);
                this.richTextBox1.TabIndex = 0;
                this.richTextBox1.Text = "";
                // 
                // LogViewer
                // 
                this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
                this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
                this.ClientSize = new System.Drawing.Size(284, 261);
                this.Controls.Add(this.richTextBox1);
                this.Name = "LogViewer";
                this.Text = "LogViewer";
                this.ResumeLayout(false);

            }

            #endregion

            private System.Windows.Forms.RichTextBox richTextBox1;
        }
    }
    ------
    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;

    namespace Investigate_IGDM_in_Log_Viewer
    {
        public partial class LogViewer : Form
        {
            public delegate void LogViewerDelegate(string msg, System.Drawing.Color color);
            public bool bClosed = false;
            public LogViewer()
            {
                InitializeComponent();
                richTextBox1.Multiline = true;
            }

            protected override void OnClosing(CancelEventArgs eventargs)
            {
                bClosed = true;
                base.OnClosing(eventargs);
                return;
            }

            public void AddLine(string msg, System.Drawing.Color color)
            {
                try
                {
                    if (bClosed)
                        return;
                    if (richTextBox1.InvokeRequired)
                        this.EndInvoke(this.BeginInvoke(new LogViewerDelegate(LogViewerCodeSegment), msg, color));
                    else
                    {
                        richTextBox1.ForeColor = color;   // the joy of this parameter is that it changes all text, not just the line being drawn next
                        richTextBox1.AppendText(msg + "\r\n");
                        //richTextBox1.ScrollToCaret();              <-------------------- uncomment this line and watch the program crash
                    }
                }
                catch
                {
                    bClosed = true;
                }
            }

            private void LogViewerCodeSegment(string msg, System.Drawing.Color color)
            {
                richTextBox1.ForeColor = color;
                richTextBox1.AppendText(msg + "\r\n");
               // richTextBox1.ScrollToCaret();   <-------------------- uncomment this line and watch the program crash
            }
        }
    }
    ------
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;

    namespace Investigate_IGDM_in_Log_Viewer
    {
        public class RegisterException
        {
            public static LogViewer LV = null;
            public static void WriteLogEntry(string message, System.Drawing.Color color)
            {
                if (LV == null)
                {
                    System.Threading.Thread t1 = new System.Threading.Thread(new System.Threading.ThreadStart(LogViewerThread));
                    t1.Start();
                }
                while (LV == null)
                    System.Threading.Thread.Sleep(1000);
                LV.AddLine(message, color);
                return;
            }

            public static void WriteErrorLogEntry(string message, System.Drawing.Color color)
            {
                WriteLogEntry(message, color);
                return;
            }

            private static void LogViewerThread()
            {
                LV = new LogViewer();
                LV.ShowDialog();
                return;
            }
        }
    }

    MARK D ROCKMAN

    Tuesday, March 19, 2013 9:22 PM

All replies

  • Hi Mark,

    I have tried this code, it doesn't crash. It just keeps writing numbers.

    Best regards,


    Mike Feng
    MSDN Community Support | Feedback to us
    Develop and promote your apps in Windows Store
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Wednesday, March 20, 2013 2:10 PM
  • Did you uncomment the indicated lines?

    MARK D ROCKMAN

    Wednesday, March 20, 2013 7:13 PM
  • Did you uncomment the indicated lines?

    MARK D ROCKMAN

    Of course.

    And My machine is win7 64bit + VS2010 .net 4.0


    Mike Feng
    MSDN Community Support | Feedback to us
    Develop and promote your apps in Windows Store
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.


    • Edited by Mike Feng Thursday, March 21, 2013 5:57 AM
    Thursday, March 21, 2013 5:57 AM
  • I'm running Windows 8 and Windows 7.   I'm running .NET Framework 4.5 and Visual Studio Professional 2012.  And by "crash" I mean the program goes into a hard compute loop after showing a number between 2000 and 4000.   I've tried this on three different computers.  Same results.  

    MARK D ROCKMAN

    Thursday, March 21, 2013 11:44 AM
  • If you let it run long enough it will go into a compute loop.   Start the numbers rolling and go for a cup of coffee.   I've seen numbers above 11291 before the race condition does the damage.

    MARK D ROCKMAN

    Thursday, March 21, 2013 4:03 PM
  • Hi Mark,

    I followed your suggestion, just drink a cup of coffee, yes, it stopped running at 20000+.

    Now I changed a little your code:

            public static LogViewer LV = null;
            public static void WriteLogEntry(string message, System.Drawing.Color color)
            {
                if (LV == null)
                {
                    //System.Threading.Thread t1 = new System.Threading.Thread(new System.Threading.ThreadStart(LogViewerThread));
                    //t1.Start();
                    LogViewerThread();
                }
                while (LV == null)
                    System.Threading.Thread.Sleep(1000);
                LV.AddLine(message, color);
                Application.DoEvents();
                return;
            }
    
            public static void WriteErrorLogEntry(string message, System.Drawing.Color color)
            {
                WriteLogEntry(message, color);
                return;
            }
    
            private static void LogViewerThread()
            {
                LV = new LogViewer();
                LV.Show();
                return;
            }


    And it works well, because I just put all things in the same thread.

    And I found not that line "richTextBox1.ScrollToCaret();" cause this crash. It is just coincidence.

    To exactly reproduce this scenario, you can replace it with

    System.Threading.Thread.Sleep(1000);

    It will stop at the second loop in the button click event.

    I hope I explained this clearly.

    Best regards,


    Mike Feng
    MSDN Community Support | Feedback to us
    Develop and promote your apps in Windows Store
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Friday, March 22, 2013 6:00 AM
  • Excellent workaround.  Thank you.  I think, however, that the original code should work and that

    there is something wrong with BeginInvoke.  It has been shown that pulling the Caret line out

    causes the program to function normally.   This may be an unimportant observation.

    We have a class RegisterException in the Investigate_IGDM_in_Log_Viewer namespace that

    exposes two nearly identical methods:

    WriteLogEntry and WriteErrorLogEntry.  These methods detect whether a separate thread has

    been created to take charge of updates to a RichTextBox control of the LogViewer class which is

    derived from the .NET Form class.  The separate thread is necessary because the rules of the

    game dictate that the message pump thread that creates the form and its controls must

    be freely able to handle events while another thread takes care of business unrelated to

    presentation issues.   This latter thread still needs to be able to update the form, but is forbidden

    to do so absent usage of BeginInvoke that ensures the message pump thread handles the actual

    updates as specified in a delegate method that the business thread calls.  If the second thread is

    eliminated it follows that any race condition is also eliminated.  It also follows that the

    message pump goes unserviced except at inconvenient times.  The form will not update

    except when the message pump is run.  The debugger says the compute loop that kills the

    program is entered from the line where BeginInvoke is called.

    We could be circling on a spin lock that has inadvertently been left set because of a race

    condition.   No way to tell without reference to the .NET Framework source code and tools that

    pinpoint the area of concern.


    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Windows.Forms;

    namespace Investigate_IGDM_in_Log_Viewer
    {
        static class Program
        {
            /// <summary>
            /// The main entry point for the application.
            /// </summary>
            [STAThread]
            static void Main()
            {
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
                Application.Run(new Form1());
            }
        }
    }

    namespace Investigate_IGDM_in_Log_Viewer
    {
        partial class Form1
        {
            /// <summary>
            /// Required designer variable.
            /// </summary>
            private System.ComponentModel.IContainer components = null;

            /// <summary>
            /// Clean up any resources being used.
            /// </summary>
            /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
            protected override void Dispose(bool disposing)
            {
                if (disposing && (components != null))
                {
                    components.Dispose();
                }
                base.Dispose(disposing);
            }

            #region Windows Form Designer generated code

            /// <summary>
            /// Required method for Designer support - do not modify
            /// the contents of this method with the code editor.
            /// </summary>
            private void InitializeComponent()
            {
                this.button1 = new System.Windows.Forms.Button();
                this.SuspendLayout();
                //
                // button1
                //
                this.button1.Location = new System.Drawing.Point(197, 12);
                this.button1.Name = "button1";
                this.button1.Size = new System.Drawing.Size(75, 23);
                this.button1.TabIndex = 0;
                this.button1.Text = "button1";
                this.button1.UseVisualStyleBackColor = true;
                this.button1.Click += new System.EventHandler(this.button1_Click);
                //
                // Form1
                //
                this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
                this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
                this.ClientSize = new System.Drawing.Size(284, 261);
                this.Controls.Add(this.button1);
                this.Name = "Form1";
                this.Text = "Form1";
                this.ResumeLayout(false);

            }

            #endregion

            private System.Windows.Forms.Button button1;
        }
    }


    namespace Investigate_IGDM_in_Log_Viewer
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
            }

            private void button1_Click(object sender, EventArgs e)
            {
                Int64 i;
                for (i = 0; ; i++)
                    RegisterException.WriteLogEntry(i.ToString(), Color.Green);
            }
        }
    }

    namespace Investigate_IGDM_in_Log_Viewer
    {
        partial class LogViewer
        {
            /// <summary>
            /// Required designer variable.
            /// </summary>
            private System.ComponentModel.IContainer components = null;

            /// <summary>
            /// Clean up any resources being used.
            /// </summary>
            /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
            protected override void Dispose(bool disposing)
            {
                if (disposing && (components != null))
                {
                    components.Dispose();
                }
                base.Dispose(disposing);
            }

            #region Windows Form Designer generated code

            /// <summary>
            /// Required method for Designer support - do not modify
            /// the contents of this method with the code editor.
            /// </summary>
            private void InitializeComponent()
            {
                this.richTextBox1 = new System.Windows.Forms.RichTextBox();
                this.SuspendLayout();
                //
                // richTextBox1
                //
                this.richTextBox1.Location = new System.Drawing.Point(13, 13);
                this.richTextBox1.Name = "richTextBox1";
                this.richTextBox1.Size = new System.Drawing.Size(246, 236);
                this.richTextBox1.TabIndex = 0;
                this.richTextBox1.Text = "";
                //
                // LogViewer
                //
                this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
                this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
                this.ClientSize = new System.Drawing.Size(284, 261);
                this.Controls.Add(this.richTextBox1);
                this.Name = "LogViewer";
                this.Text = "LogViewer";
                this.ResumeLayout(false);

            }

            #endregion

            private System.Windows.Forms.RichTextBox richTextBox1;
        }
    }


    namespace Investigate_IGDM_in_Log_Viewer
    {
        public partial class LogViewer : Form
        {
            public delegate void LogViewerDelegate(string msg, System.Drawing.Color color);
            public bool bClosed = false;
            public LogViewer()
            {
                InitializeComponent();
                richTextBox1.Multiline = true;
            }

            protected override void OnClosing(CancelEventArgs eventargs)
            {
                bClosed = true;
                base.OnClosing(eventargs);
                return;
            }

            public void AddLine(string msg, System.Drawing.Color color)
            {
                try
                {
                    if (bClosed)
                        return;
                    if (richTextBox1.InvokeRequired)
                        richTextBox1.EndInvoke(richTextBox1.BeginInvoke(new LogViewerDelegate(LogViewerCodeSegment), msg, color));
                    else
                    {
                        richTextBox1.ForeColor = color;   // the joy of this parameter is that it changes all text, not just the line being drawn next
                        richTextBox1.AppendText(msg + "\r\n");
                        richTextBox1.ScrollToCaret();
                    }
                }
                catch
                {
                    bClosed = true;
                }
            }

            private void LogViewerCodeSegment(string msg, System.Drawing.Color color)
            {
                richTextBox1.ForeColor = color;
                richTextBox1.AppendText(msg + "\r\n");
                richTextBox1.ScrollToCaret();
            }
        }
    }


    namespace Investigate_IGDM_in_Log_Viewer
    {
        public class RegisterException
        {
            public static LogViewer LV = null;
            public static void WriteLogEntry(string message, System.Drawing.Color color)
            {
                if (LV == null)
                {
                    System.Threading.Thread t1 = new System.Threading.Thread(new System.Threading.ThreadStart(LogViewerThread));
                    t1.Start();
                }
                while (LV == null)
                    System.Threading.Thread.Sleep(1000);
                LV.AddLine(message, color);
                return;
            }

            public static void WriteErrorLogEntry(string message, System.Drawing.Color color)
            {
                WriteLogEntry(message, color);
                return;
            }

            private static void LogViewerThread()
            {
                LV = new LogViewer();
                LV.ShowDialog();
                return;
            }
        }
    }


    MARK D ROCKMAN

    Friday, March 22, 2013 9:32 AM
  • Hi Mark,

    Based on my understanding, that you need a separate thread to write log.

    If so, I suggest you make a mutex to sync the two threads: http://msdn.microsoft.com/en-us/library/system.threading.mutex.aspx

    Before you actually write the log in RichtextBox, you can the Waitone() method at the very beginning of  WriteLogEntry method.

    And Release it after you write logs.

    Please take a look at this code:

            public static Mutex sign = new Mutex(true);
            public static void WriteLogEntry(string message, System.Drawing.Color color)
            {
                sign.WaitOne(-1);
                if (LV == null)
                {
                    System.Threading.Thread t1 = new System.Threading.Thread(new System.Threading.ThreadStart(LogViewerThread));
                    t1.Start();
                    //LogViewerThread();
                }
                while (LV == null)
                    System.Threading.Thread.Sleep(1000);
                LV.AddLine(message, color);
                //Application.DoEvents();
                return;
            }

    And then:

            private static void LogViewerThread()
            {
                LV = new LogViewer();
                LV.ShowDialog();
                return;
            }

            public void AddLine(string msg, System.Drawing.Color color)
            {
                try
                {
                    if (bClosed)
                        return;
                    if (richTextBox1.InvokeRequired)
                        this.EndInvoke(this.BeginInvoke(new LogViewerDelegate(LogViewerCodeSegment), msg, color));
                    else
                    {
                        this.LogViewerCodeSegment(msg, color);
                        //richTextBox1.ForeColor = color;   // the joy of this parameter is that it changes all text, not just the line being drawn next
                        //richTextBox1.AppendText(msg + "\r\n");
                        //richTextBox1.ScrollToCaret();             // <-------------------- uncomment this line and watch the program crash
                    }
                }
                catch
                {
                    bClosed = true;
                }
                RegisterException.sign.ReleaseMutex();
            }
    Best regards,

    Mike Feng
    MSDN Community Support | Feedback to us
    Develop and promote your apps in Windows Store
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Friday, March 22, 2013 10:09 AM
  • As you can see, I have added your mutual exclusion lock logic to the program.  I moved the ReleaseMutex() from the AddLine method to the place in the WriteLogEntry method immediately following the one and only call to the AddLine method, to guarantee that the mutex will be released, even in the case where bClose is true.   Since we are now, once again, using BeginInvoke(), testing shows that the infinite loop recurs.    What follows is a summary of changes that I made to previous code sample and then a complete recapitulation of the code as it stands now.   We're still watching the program get hung in a compute loop on a call to BeginInvoke().

    -196,198
                        LogViewerCodeSegment(msg, color);
    -220
            public static System.Threading.Mutex mutualExclusionLock = new System.Threading.Mutex(true);
    -223
                const bool bUsingMutex = true;
                if (bUsingMutex)
                    mutualExclusionLock.WaitOne(-1);
    -231
                if (bUsingMutex)
                    mutualExclusionLock.ReleaseMutex();

    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;

    namespace Investigate_IGDM_in_Log_Viewer
    {
        static class Program
        {
            /// <summary>
            /// The main entry point for the application.
            /// </summary>
            [STAThread]
            static void Main()
            {
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
                Application.Run(new Form1());
                //  Application.DoEvents();
            }
        }
    }

    namespace Investigate_IGDM_in_Log_Viewer
    {
        partial class Form1
        {
            /// <summary>
            /// Required designer variable.
            /// </summary>
            private System.ComponentModel.IContainer components = null;

            /// <summary>
            /// Clean up any resources being used.
            /// </summary>
            /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
            protected override void Dispose(bool disposing)
            {
                if (disposing && (components != null))
                {
                    components.Dispose();
                }
                base.Dispose(disposing);
            }

            #region Windows Form Designer generated code

            /// <summary>
            /// Required method for Designer support - do not modify
            /// the contents of this method with the code editor.
            /// </summary>
            private void InitializeComponent()
            {
                this.button1 = new System.Windows.Forms.Button();
                this.SuspendLayout();
                //
                // button1
                //
                this.button1.Location = new System.Drawing.Point(197, 12);
                this.button1.Name = "button1";
                this.button1.Size = new System.Drawing.Size(75, 23);
                this.button1.TabIndex = 0;
                this.button1.Text = "button1";
                this.button1.UseVisualStyleBackColor = true;
                this.button1.Click += new System.EventHandler(this.button1_Click);
                //
                // Form1
                //
                this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
                this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
                this.ClientSize = new System.Drawing.Size(284, 261);
                this.Controls.Add(this.button1);
                this.Name = "Form1";
                this.Text = "Form1";
                this.ResumeLayout(false);

            }

            #endregion

            private System.Windows.Forms.Button button1;
        }
    }


    namespace Investigate_IGDM_in_Log_Viewer
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
            }

            private void button1_Click(object sender, EventArgs e)
            {
                Int64 i;
                for (i = 0; ; i++)
                    RegisterException.WriteLogEntry(i.ToString(), Color.Green);
            }
        }
    }

    namespace Investigate_IGDM_in_Log_Viewer
    {
        partial class LogViewer
        {
            /// <summary>
            /// Required designer variable.
            /// </summary>
            private System.ComponentModel.IContainer components = null;

            /// <summary>
            /// Clean up any resources being used.
            /// </summary>
            /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
            protected override void Dispose(bool disposing)
            {
                if (disposing && (components != null))
                {
                    components.Dispose();
                }
                base.Dispose(disposing);
            }

            #region Windows Form Designer generated code

            /// <summary>
            /// Required method for Designer support - do not modify
            /// the contents of this method with the code editor.
            /// </summary>
            private void InitializeComponent()
            {
                this.richTextBox1 = new System.Windows.Forms.RichTextBox();
                this.SuspendLayout();
                //
                // richTextBox1
                //
                this.richTextBox1.Location = new System.Drawing.Point(13, 13);
                this.richTextBox1.Name = "richTextBox1";
                this.richTextBox1.Size = new System.Drawing.Size(246, 236);
                this.richTextBox1.TabIndex = 0;
                this.richTextBox1.Text = "";
                //
                // LogViewer
                //
                this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
                this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
                this.ClientSize = new System.Drawing.Size(284, 261);
                this.Controls.Add(this.richTextBox1);
                this.Name = "LogViewer";
                this.Text = "LogViewer";
                this.ResumeLayout(false);

            }

            #endregion

            private System.Windows.Forms.RichTextBox richTextBox1;
        }
    }


    namespace Investigate_IGDM_in_Log_Viewer
    {
        public partial class LogViewer : Form
        {
            public delegate void LogViewerDelegate(string msg, System.Drawing.Color color);
            public bool bClosed = false;
            public LogViewer()
            {
                InitializeComponent();
                richTextBox1.Multiline = true;
            }

            protected override void OnClosing(CancelEventArgs eventargs)
            {
                bClosed = true;
                base.OnClosing(eventargs);
                return;
            }

            public void AddLine(string msg, System.Drawing.Color color)
            {
                try
                {
                    if (bClosed)
                        return;
                    if (richTextBox1.InvokeRequired)
                        richTextBox1.EndInvoke(richTextBox1.BeginInvoke(new LogViewerDelegate(LogViewerCodeSegment), msg, color));
                    else
                    {
                        LogViewerCodeSegment(msg, color);
                    }
                }
                catch
                {
                    bClosed = true;
                }
            }

            private void LogViewerCodeSegment(string msg, System.Drawing.Color color)
            {
                richTextBox1.ForeColor = color;
                richTextBox1.AppendText(msg + "\r\n");
                richTextBox1.ScrollToCaret();
            }
        }
    }


    namespace Investigate_IGDM_in_Log_Viewer
    {
        public class RegisterException
        {
            public static System.Threading.Mutex mutualExclusionLock = new System.Threading.Mutex(true);
            public static LogViewer LV = null;
            public static void WriteLogEntry(string message, System.Drawing.Color color)
            {
                const bool bUsingMutex = true;
                if (bUsingMutex)
                    mutualExclusionLock.WaitOne(-1);
                if (LV == null)
                {
                    System.Threading.Thread t1 = new System.Threading.Thread(new System.Threading.ThreadStart(LogViewerThread));
                    t1.Start();
                }
                while (LV == null)
                    System.Threading.Thread.Sleep(1000);
                LV.AddLine(message, color);
                if (bUsingMutex)
                    mutualExclusionLock.ReleaseMutex();
                return;
            }

            public static void WriteErrorLogEntry(string message, System.Drawing.Color color)
            {
                WriteLogEntry(message, color);
                return;
            }

            private static void LogViewerThread()
            {
                LV = new LogViewer();
                LV.ShowDialog();
                return;
            }
        }
    }

    /*
    We have a class RegisterException in the Investigate_IGDM_in_Log_Viewer namespace that exposes two nearly identical methods:
    WriteLogEntry and WriteErrorLogEntry.  These methods detect whether a separate thread has been created to take charge of
    updates to a RichTextBox control of the LogViewer class which is derived from the .NET Form class.  The separate thread is
    necessary because the rules of the game dictate that the message pump thread that creates the form and its controls must
    be permitted to handle events while another thread takes care of business unrelated to presentation issues.   This latter
    thread still needs to be able to update the form, but is forbidden to do so absent usage of BeginInvoke that ensures the
    message pump thread handles the actual updates as specified in a delegate method that the business thread calls.
    If the second thread is eliminated it follows that any race condition is also eliminated.  It also follows that the
    message pump goes unserviced except at inconvenient times.  The form will not update except when the message pump is run.
    The debugger says the compute loop that kills the program is entered from the line where BeginInvoke is called.
    We could be circling on a spin lock that has inadvertently been left set because of a race condition.   No way to tell
    without reference to the .NET Framework source code and tools that pinpoint the area of concern.
    */


    MARK D ROCKMAN

    Friday, March 22, 2013 2:50 PM
  • Hi Mark,

    Didn't reproduce this scenario, here is my test project  on skydrive: http://sdrv.ms/16SKbyB 

    Best regards,


    Mike Feng
    MSDN Community Support | Feedback to us
    Develop and promote your apps in Windows Store
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Monday, March 25, 2013 6:01 AM
  • Hello Mike,

    I downloaded your test project and ran it twice.  Both times it ended in a hard compute loop.

    Best wishes,

    Mark


    MARK D ROCKMAN

    Tuesday, March 26, 2013 12:53 AM
  • Hello Mike,

    I downloaded your test project and ran it twice.  Both times it ended in a hard compute loop.

    Best wishes,

    Mark


    MARK D ROCKMAN

    Hi Mark,

    How long you encounter this scenario?  A few minutes? Or hours?

    Best regards,


    Mike Feng
    MSDN Community Support | Feedback to us
    Develop and promote your apps in Windows Store
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Tuesday, March 26, 2013 3:13 AM
  • It takes as long as it takes.   This time the counter got up over 21000.   It is like that with race conditions.  I've identified a solid bug in Microsoft's .NET Framework or an error of omission in its documentation.   At no time have I ever run the program without a failure showing its ugly face.  When you add 2 plus 2 and get 2:  that's a race condition.

    MARK D ROCKMAN

    Tuesday, March 26, 2013 10:49 AM
  • Use the standard InvokeRequired pattern.  Use Invoke, not BeginInvoke.  You have no code to prevent the data you are passing cross-thread from changing during the cross-tread call.

    Here's a simpler example that essentially mimics your code:

    using System;
    using System.Windows.Forms;
    using System.ComponentModel; namespace WindowsFormsApplication1
    {
      
    public  class Form1 : Form
      {
        
    RichTextBox RTB = new RichTextBox();
        
    BackgroundWorker BGW = new BackgroundWorker();
        
    protected override void OnLoad(EventArgs e)
        {
          
    base.OnLoad(e);
          RTB.Dock = 
    DockStyle.Fill;
          RTB.Parent = 
    this;
          BGW.WorkerReportsProgress = 
    true;
          BGW.ProgressChanged += BGW_Progress;
          BGW.DoWork += BGW_DoWork;
          BGW.RunWorkerAsync();
        }
        
    public void BGW_DoWork(object sender, DoWorkEventArgs e)
        {
          
    int I = 0;
          
    do
          {
            I += 1;
            
    string S = "This is string number " + I.ToString() + ".";
            
    //System.Threading.Thread.Sleep(1);  Increasing the sleep value will
            
    //increase the time before it crashes.
            BGW.ReportProgress(0, S);
          } 
    while (true);
        }
        
    public void BGW_Progress(object sender, ProgressChangedEventArgs e)
        {
          RTB.AppendText(
    Convert.ToString(e.UserState) + Environment.NewLine);
          RTB.ScrollToCaret();
        }
       }
    }

    • Edited by JohnWein Tuesday, March 26, 2013 8:11 PM
    Tuesday, March 26, 2013 3:18 PM
  • As recommended, I removed the EndInvoke/BeginInvoke and replaced same with plain old Invoke.

    That also crashes.   I reproduce the revised code below.   I'm not wedded to this approach.   I think it should not crash, however.

    It does take some operating time before the crash happens.   The amount of time varies.

    Crash is defined as a hard compute loop at the Invoke.

    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.Threading;

    namespace WinFormsApp_RichTextBox
    {
        public partial class LogViewer : Form
        {
            public delegate void LogViewerDelegate(string msg, System.Drawing.Color color);
            public bool bClosed = false;
            public LogViewer()
            {
                InitializeComponent();
                richTextBox1.Multiline = true;
            }

            protected override void OnClosing(CancelEventArgs eventargs)
            {
                try
                {
                    bClosed = true;
                    base.OnClosing(eventargs);
                    //return;
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }
            }

            public void AddLine(string msg, System.Drawing.Color color)
            {
                try
                {
                    if (bClosed)
                        return;
                    if (richTextBox1.InvokeRequired)
                        Invoke(new LogViewerDelegate(LogViewerCodeSegment), msg, color);
                    else
                    {
                        this.LogViewerCodeSegment(msg, color);
                        //richTextBox1.ForeColor = color;   // the joy of this parameter is that it changes all text, not just the line being drawn next
                        //richTextBox1.AppendText(msg + "\r\n");
                        //richTextBox1.ScrollToCaret();             // <-------------------- uncomment this line and watch the program crash
                    }
                }
                catch
                {
                    bClosed = true;
                }
            }

            private void LogViewerCodeSegment(string msg, System.Drawing.Color color)
            {
                richTextBox1.ForeColor = color;
                richTextBox1.AppendText(msg + "\r\n");
                System.Threading.Thread.Sleep(100);
                richTextBox1.ScrollToCaret();   //<-------------------- uncomment this line and watch the program crash
            }
        }
    }


    MARK D ROCKMAN

    Tuesday, March 26, 2013 9:49 PM
  • There should be something different about a crash using BeginInvoke and Invoke.  I didn't look carefully at your code, but the Invoke should stop processing on the calling thread.  Do you have more than one thread other than the UI thread?  It might also be ScrollToCaret which involves calling the OS and painting.  One thing for sure:  it's not going to change.  The RichEdit control's operation is cast in stone.
    Tuesday, March 26, 2013 10:12 PM
  • There is the original thread, created when the program is launched, and then there is a second thread that Invokes the first thread so as to update the controls; all of which are created by the first thread.   That's all.  Without the ScrollToCaret call, the program does not go into hard compute loop.

    MARK D ROCKMAN

    Tuesday, March 26, 2013 11:21 PM
  • Something's screwy.  Invoke should not crash.  How does you code differ from this code which never crashes:

    using System;
    using System.Windows.Forms;
    using System.ComponentModel; namespace WindowsFormsApplication1
    {
      
    public class Form1 : Form
      {
        
    RichTextBox RTB = new RichTextBox();
        
    BackgroundWorker BGW = new BackgroundWorker();
        
    protected override void OnLoad(EventArgs e)
        {
          
    base.OnLoad(e);
          RTB.Dock = 
    DockStyle.Fill;
          RTB.Parent = 
    this;
          BGW.WorkerReportsProgress = 
    true;
          BGW.DoWork += BGW_DoWork;
          BGW.RunWorkerAsync();
        }
        
    public void BGW_DoWork(object sender, DoWorkEventArgs e)
        {
          
    int I = 0;
          
    do
          {
            I += 1;
            
    string S = "This is string number " + I.ToString() + ".";
            
    this.Invoke(new CallAppendText(BGW_Progress),S);
          } 
    while (true);
        }
        
    delegate void CallAppendText(string text);
        
    public void BGW_Progress(string text)
        {
          RTB.AppendText(text+ 
    Environment.NewLine);
          RTB.ScrollToCaret();
        }
      }
    }


    • Edited by JohnWein Wednesday, March 27, 2013 1:29 AM
    Wednesday, March 27, 2013 1:28 AM
  • You say your code never crashes.  You know:  the code that uses the BackgroundWorker class.   But I put it together on my Windows 8 system and it crashes reliably.  (Crash is used as shorthand for "goes into an infinite compute loop.")

    MARK D ROCKMAN

    Wednesday, March 27, 2013 12:15 PM
  • "(Crash is used as shorthand for "goes into an infinite compute loop.")"

    Do you mean that their is no error, it simply executes the code as it is written?

    The BeginInvoke version crashes with an error.

    Wednesday, March 27, 2013 12:26 PM
  • I mean both the original program and your revision of it terminate in exactly the same way.   They stop updating the RichTextBox and is heard from no more.   If you click the caption bar of the window where the numbers appear, Not Responding appears in the caption bar.   The program must be terminated by manual intervention.   This is not a one-off.   It happens every time the program is launched.

    MARK D ROCKMAN

    Wednesday, March 27, 2013 12:58 PM
  • If you write a program to do this by swamping the UI thread, this is exactly what you should expect to happen.  Using BeginInvoke, you have to limit updates of the UI to about 10 per second and pass an object that can be cleared, disposed or set to nothing so the background thread can detect when it is safe to make another UI thread call.  I'll modify my BeginInvoke example to show how it can be done.
    Wednesday, March 27, 2013 1:14 PM
  • Must be a documentation oversight.

    MARK D ROCKMAN

    Wednesday, March 27, 2013 2:56 PM
  • Must be a documentation oversight.

    MARK D ROCKMAN

    I'm sure it's covered in most discussions of Windows messaging.  If this is the first time you've seen a non-responsive UI element, that's very unusual.  Any time an application blocks messages from the OS for more than a few seconds, the app will be marked as unresponsive and it's main title will indicate that it's not responding.
    Wednesday, March 27, 2013 3:26 PM
  • Updating the UI thread using BeginInvoke from a background thread isn't the simplest pattern:

    using System;
    using System.Windows.Forms;
    using System.ComponentModel; using System.Collections.Generic;
    using System.Threading;
    namespace WindowsFormsApplication1 {   public class Form1 : Form
      {
        
    RichTextBox RTB = new RichTextBox();
        
    BackgroundWorker BGW = new BackgroundWorker();
        
    protected override void OnLoad(EventArgs e)
        {
          
    base.OnLoad(e);
          RTB.Dock = 
    DockStyle.Fill;
          RTB.Parent = 
    this;
          BGW.WorkerReportsProgress = 
    true;
          BGW.ProgressChanged += BGW_Progress;
          BGW.RunWorkerCompleted += BGW_Done;
          BGW.WorkerSupportsCancellation = 
    true;
          BGW.DoWork += BGW_DoWork;
          BGW.RunWorkerAsync();
        }
        
    public void BGW_DoWork(object sender, DoWorkEventArgs e)
        {
          
    List<string> currentList = new List<string>();
          
    List<string> lastList = new List<string>();
          
    DateTime lastTime = DateTime.Now;
          
    int i = 0;
          
    while (i < 999999)
          {
            
            currentList.Add( 
    "This is string number " + i++.ToString() + ".");
            
    Thread.Sleep(1);  //Simulates processing
            
    DateTime currentTime = DateTime.Now;
            
    if (currentTime.Subtract(lastTime).TotalMilliseconds > 100.0)
            {
              lastList.AddRange(currentList.ToArray());
              currentList.Clear();
              BGW.ReportProgress(0,lastList);
              
    while (lastList.Count > 0) Thread.Sleep(10);
              
    if (BGW.CancellationPending) break;
            }
          }
        }
        
    public void BGW_Progress(object sender, ProgressChangedEventArgs e)
        {
          
    List<string> lastList = (List<string>)e.UserState;
          RTB.AppendText(
    string.Join(Environment.NewLine, lastList.ToArray()));
          RTB.AppendText(
    Environment.NewLine);
          lastList.Clear();
          RTB.ScrollToCaret();
        }
        
    public void BGW_Done(object sender, RunWorkerCompletedEventArgs e)
        {
          
    this.Close();
        }
        
    protected override void OnFormClosing(FormClosingEventArgs e)
        {
           
    base.OnFormClosing(e);
           
    if (BGW.IsBusy) 
           {
             e.Cancel = 
    true;
             BGW.CancelAsync();
           }
        }
      }
    }

    Wednesday, March 27, 2013 3:51 PM
  • http://www.mdrsesco.biz/JosephAndMary.zip

    contains the source code and a stack trace.    Program seems to work okay without the ScrollToCaret() call, as before.


    MARK D ROCKMAN

    Wednesday, March 27, 2013 11:53 PM
  • Hi Mark,

    I have download your code and run it several times, it closed normally, although it took me a lot of time to test it.

    Best regards,


    Mike Feng
    MSDN Community Support | Feedback to us
    Develop and promote your apps in Windows Store
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Tuesday, April 2, 2013 3:32 PM