cross-thread access error in non-crossing scenario

SyntaxEditor for Windows Forms Forum

Posted 12 years ago by orders_contact - InRule Technology
Version: 4.0.0262
Platform: .NET 2.0
Environment: Windows Vista (32-bit)
Avatar
I have observed a cross-thread access violation when using *SEPARATE* instances of the Actipro SyntaxEditor on two different threads consecutively (NOT simultaneously, nor in a cross-thread manner!). Please note, in version 4.0.260 of the editor, where I originally observed this problem, the error was an InvalidOperationException due to cross-thread access, but in the latest version 4.0.262 it manifests as an OutOfMemoryException. I have listed code below which is an atomic reproduction of this issue but here are the basic steps:

1) create a UserControl "A" containing the Actipro SyntaxEditor and set the editor's Border property in the default constructor of the Control.
2) create another UserControl "B" which creates an instance of "A" via reflection (System.Activator.CreateInstance) and adds it to its Controls collection
3) In a static (ApartmentState.STA) thread, create the second control and do something with it
4) Repeat step 3 again on a new STA thread and you will get an *inner* exception (wrapped in a TargetInvocationException thanks to the Activator) which manifests as either cross-thread error or out of memory error depending on your Actipro version as I noted above.

Below is a self contained sample illustrating this issue or if you prefer I can email a project with this in it. I realize this sample code looks a little strange, but it is a minimal repro case, rather than the more sensible looking pattern where I discovered this issue.

Please let me know if you need more information in order to identify/fix this issue.

Thanks,
John Hauppa

//Required references:
//  - ActiproSoftware.Shared.Net20 (1.0.93.0)
//  - ActiproSoftware.SyntaxEditor.Net20 (4.0.262.0)
//  - ActiproSoftware.WinUICore.Net20 (1.0.93.0)
//  - System
//  - System.Drawing
//  - System.Windows.Forms

using System;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;
using ActiproSoftware.Drawing;
using ActiproSoftware.SyntaxEditor;

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

                //some static GUI element inside Actipro seems to get created on this first thread...
                StaticThreadRunner.RunStaticThread(ActiproIndirectActivator);

                //the second thread fails due to cross-thread access of some GUI element...
                StaticThreadRunner.RunStaticThread(ActiproIndirectActivator);
            }
            catch(Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
        }

        //STA thread for manipulating our UserControl because it must support drag-n-drop/OLE
        //This thread creates our control, which uses System.Activator to create another control
        //containing the Actipro editor control.
        [STAThread]
        private static void ActiproIndirectActivator()
        {
            IndirectControlHost control = new IndirectControlHost();
         
            //nothing interesting is going on here, just illustrating the cross-thread issue
            control.Focus();
        }   
    }

    //Utility for executing functions on an STA thread
    public class StaticThreadRunner
    {
        //Runs the supplied threadstart as STA thread and always reports thread
        //exceptions
        public static void RunStaticThread(ThreadStart threadMain)
        {
            StaticThreadParameter parameter = new StaticThreadParameter(threadMain);

            Thread thread = new Thread(new ParameterizedThreadStart(ParameterizedStaticThreadMain));
            thread.TrySetApartmentState(ApartmentState.STA);
            thread.Start(parameter);
            thread.Join();

            if (parameter.Failed)
            {
                throw parameter.Exception;
            }
        }

        private static void ParameterizedStaticThreadMain(object obj)
        {
            StaticThreadParameter parameter = (StaticThreadParameter)obj;
            parameter.Execute();
        }

        private class StaticThreadParameter
        {
            private Exception _exception;
            private ThreadStart _threadMain;

            public StaticThreadParameter(ThreadStart threadMain)
            {
                _exception = null;
                _threadMain = threadMain;
            }

            public Exception Exception
            {
                get { return _exception; }
            }

            //ensures thread exceptions get returned gracefully
            public void Execute()
            {
                try
                {
                    _threadMain.Invoke();
                }
                catch (Exception ex)
                {
                    _exception = ex;
                }
            }

            public bool Failed
            {
                get { return _exception != null; }
            }
        }
    }

    //This control is to be used on STA threads and using reflection/System.Activator
    //to create an instance of another control containing the Actipro editor
    public class IndirectControlHost : UserControl
    {
        public IndirectControlHost()
        {
            this.SuspendLayout();
            // 
            // IndirectControlHost
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.Name = "IndirectControlHost";
            this.Size = new System.Drawing.Size(180, 180);
            this.ResumeLayout(false);

            ActiproHostControl myControl = (ActiproHostControl)Activator.CreateInstance(typeof(ActiproHostControl));
            myControl.Dock = DockStyle.Fill;
            this.Controls.Add(myControl);
        }
    }

    //This control is a simple container for the Actipro editor which sets a specific Border
    //on it; which... it would seem... is the point of cross-thread failure.
    public class ActiproHostControl : UserControl
    {
        private ActiproSoftware.SyntaxEditor.SyntaxEditor syntaxEditor1;

        public ActiproHostControl()
        {
            ActiproSoftware.SyntaxEditor.Document document1 = new ActiproSoftware.SyntaxEditor.Document();
            this.syntaxEditor1 = new ActiproSoftware.SyntaxEditor.SyntaxEditor();
            this.SuspendLayout();
            // 
            // syntaxEditor1
            // 
            this.syntaxEditor1.Dock = System.Windows.Forms.DockStyle.Fill;
            this.syntaxEditor1.Document = document1;
            this.syntaxEditor1.Location = new System.Drawing.Point(0, 0);
            this.syntaxEditor1.Name = "syntaxEditor1";
            this.syntaxEditor1.Size = new System.Drawing.Size(150, 150);
            this.syntaxEditor1.TabIndex = 0;
            // 
            // ActiproHostControl
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.Controls.Add(this.syntaxEditor1);
            this.Name = "ActiproHostControl";
            this.ResumeLayout(false);

            //turn on drag-n-drop support (OLE / STAThread)
            this.AllowDrop = true;

            //the following line causes the cross-thread exception during Activator.CreateInstance
            ((VisualStudio2005SyntaxEditorRenderer)syntaxEditor1.RendererResolved).Border =
                new SimpleBorder(SimpleBorderStyle.None, SystemColors.Window);
        }
    }
}

Comments (1)

Posted 12 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
Hi John,

Thanks for the code, I could repro it in both old and current versions.

I believe it is because the global renderer change you made as your last line triggers notifications to SyntaxEditor to invalidate itself. The problem is that the global renderer is on the main thread. I think that is where the problem lies.

When I set SyntaxEditor.Renderer = new VisualStudio2005SyntaxEditorRenderer() before that border line (this gives it a control-specific renderer), I didn't get the exception.

That seems to work around this issue in both the older and current builds.


Actipro Software Support

The latest build of this product (v2018.1 build 0341) was released 6 months ago, which was after the last post in this thread.

Add Comment

Please log in to a validated account to post comments.