Search code examples
c#monomonodevelopgtk#

System.AccessViolationException occurs on TextView.Buffer


I am trying to show some text on C#, one line per second, using Gtk# GUI. the text is on .txt file, and it has 4 integers at every line.

but when I compiled it at DragonFly BSD, the first one or two lines are showed perfectly on textbox but the program halts, and I've got SIGABRT and SIGSEGV errors.

so I've compiled same code at Windows, and it has this error: Exception of 'System.AccessViolationException' or something like that.

I've checked "Allow unsafe codes", but the result was same.

async void DisplayText(string FileName)
{
    string[] Temp = File.ReadAllLines(FileName);
    string[] ScoreBoard = new string[4];

    TextIter Ti = textview.Buffer.StartIter;

    foreach (string Line in Temp)
    {
        ScoreBoard = Line.Split('\t');

        await Task.Delay(1000);

        textview.Buffer.Insert(ref Ti, ScoreBoard[0]);
        textview.Buffer.Insert(ref Ti, "  |  ");
        textview.Buffer.Insert(ref Ti, ScoreBoard[1]);
        textview.Buffer.Insert(ref Ti, "\t");
        textview.Buffer.Insert(ref Ti, ScoreBoard[2]);
        textview.Buffer.Insert(ref Ti, "  |  ");
        textview.Buffer.Insert(ref Ti, ScoreBoard[3]);
        textview.Buffer.Insert(ref Ti, "\n");
    }
}

The other parts of code works perfectly, but in this part, the error occurs.

If I delete 'async' and "await Task.Delay(1000);", it doesn't have error but I want to display it 1 lines per second.

How can I solve it?


Solution

  • Gtk# is quite temperamental and does not work very well with threads messing with the UI thread (the main one). Also, you cannot update the UI from a secondary thread (this is common for all graphic toolkits).

    If I understood your problem correctly, you could only just use System.Threading in order to sleep for a second. The problem is, if you did that, your app would be unresponsive for that whole second.

    The solution is to actively wait until a second has passed. This is less exact, than waiting for exactly a second, but I hope it will meet your needs.

    Here is the code you need:

        void ActivelyWaitFor(long targetMillis)
        {
            var stopWatch = new System.Diagnostics.Stopwatch();
    
            stopWatch.Start();
    
            while( stopWatch.ElapsedMilliseconds < targetMillis ) {
                Gtk.Application.RunIteration();
            }
    
            stopWatch.Stop();
        }
    

    Gtk can run an iteration and return (Gtk.Application.RunIteration()), which is handy for the purpose here. We can call repeately that in order to offer a responsive user interface, while we wait for the time to pass.

    And here is the whole code for a window doing the task you need, in case you have doubts about how to use it.

    public class MainWindow: Gtk.Window
    {
        public MainWindow()
            :base(Gtk.WindowType.Toplevel)
        {
            this.Build();
    
            this.DeleteEvent += (o, evt) => Gtk.Application.Quit();
            this.Shown += (o, args) => this.DisplayText();
        }
    
        void Build()
        {
            this.TextView = new Gtk.TextView();
    
            this.Add( this.TextView );
        }
    
        void DisplayText()
        {
            string[] ScoreBoard = new string[4];
            Gtk.TextIter Ti = this.TextView.Buffer.StartIter;
            string[] Temp = {
                "1\t2\t3\t4",
                "1\t2\t3\t4",
                "1\t2\t3\t4",
                "1\t2\t3\t4",
                "1\t2\t3\t4",
                "1\t2\t3\t4",
            };
    
            foreach (string Line in Temp)
            {
                ScoreBoard = Line.Split('\t');
    
                this.ActivelyWaitFor( 1000 );
    
                this.TextView.Buffer.Insert(ref Ti, ScoreBoard[0]);
                this.TextView.Buffer.Insert(ref Ti, "  |  ");
                this.TextView.Buffer.Insert(ref Ti, ScoreBoard[1]);
                this.TextView.Buffer.Insert(ref Ti, "\t");
                this.TextView.Buffer.Insert(ref Ti, ScoreBoard[2]);
                this.TextView.Buffer.Insert(ref Ti, "  |  ");
                this.TextView.Buffer.Insert(ref Ti, ScoreBoard[3]);
                this.TextView.Buffer.Insert(ref Ti, "\n");
            }
        }
    
        void ActivelyWaitFor(long targetMillis)
        {
            var stopWatch = new System.Diagnostics.Stopwatch();
    
            stopWatch.Start();
    
            while( stopWatch.ElapsedMilliseconds < targetMillis ) {
                Gtk.Application.RunIteration();
            }
    
            stopWatch.Stop();
        }
    
        private Gtk.TextView TextView;
    }
    

    Hope this helps.