使用 System.Timers.Timer 设置 GtkSharp ComboBox 时出现段错误

Segfault when setting a GtkSharp ComboBox with a System.Timers.Timer

我在使用 System.Timers 中的 Timer 对象时遇到了一些问题,在 GTK 的 C# 绑定中使用它来设置 ComboBox 的选项;我遇到段错误,我认为这是因为当 Timer 调用其中一个附加的 Elapsed 回调时,它在线程池中执行。这是问题的一些示例代码:

combobox1 是在 MonoDevelop 中使用 GUI 设计器添加的 Gtk ComboBox,MainWindow.cs:

using System;
using System.Timers;
using System.Collections.Generic;
using Gtk;

public partial class MainWindow: Gtk.Window
{
    List<string> cbOptions = new List<string>();

    public MainWindow()
        : base(Gtk.WindowType.Toplevel)
    {
        Build();
        combobox1.Sensitive = false;
    }

    protected void OnDeleteEvent(object sender, DeleteEventArgs a)
    {
        Application.Quit();
        a.RetVal = true;
    }

    public void SetOptions(List<string> options, string initial)
    {
        // Clear & repopulate
        if (options?.Count > 0)
        {
            // Clear
            for (int i = 0; i < cbOptions.Count; i++)
                combobox1.RemoveText(0);

            // Set
            foreach (string opt in options)
                combobox1.AppendText(opt);

            // Set the default
            combobox1.Active = options.IndexOf(initial);
            combobox1.Sensitive = true;
            cbOptions = options;
        }   
        else
            combobox1.Sensitive = false;
    }   
}

Program.cs:

using System;
using System.Timers;
using System.Collections.Generic;
using Gtk;

namespace ComboBoxTest
{
    class MainClass
    {
        static MainWindow win;

        public static void Main(string[] args)
        {
            Application.Init();
            win = new MainWindow();
            win.Show();

            Timer t = new Timer(2000.0);
            t.Elapsed += SetCB;
            t.Start();

            Application.Run();
        }

        public static void SetCB(object sender, ElapsedEventArgs args)
        {
            List<string> options = new List<string>();
            for (int i = 1; i <= 25; i++)
                options.Add(string.Format("{0} 000 000 000", i));

            Random r = new Random();
            win.SetOptions(options, string.Format("{0} 000 000 000", r.Next(1, 25)));
        }
    }
}

是否有某种 GUI 线程可以确保回调 运行 开启,这样我就不会遇到这个问题?如果您删除 Timer 代码并在 Application.Run(); 之前添加对 SetCB(null, null); 的调用,您将按预期执行此操作。

段错误要么是 Mono 错误,要么是在 GTK 中错误使用线程的结果。请用这个小工具来判断是不是后者:https://github.com/slluis/gui-thread-check

我知道怎么做了。如果你正在处理多个线程,你需要将你的 GTK GUI 调用封装在一个委托中,并将其传递给 Gtk.Application.Invoke().

例如

Gtk.Application.Invoke(delegate {
    comboBox1.AppendText("Option X");
});

这是安全和正确的方法。