You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Gtk#的C#程序异步显示文本触发System.AccessViolationException的解决方法

解决Gtk#异步更新UI时的线程安全问题

这个问题的核心原因很明确:Gtk的所有UI操作必须在主线程(GUI线程)中执行,而你当前的代码在await Task.Delay(1000)之后,后续的textview.Buffer.Insert操作是在非GUI线程执行的,这直接违反了Gtk的线程模型,导致了内存访问错误(不管是BSD下的SIGABRT/SIGSEGV还是Windows的AccessViolationException,本质都是线程不安全的UI访问)。

和WinForms/WPF不同,Gtk并没有默认的SynchronizationContext来帮你把await后的代码切回GUI线程,所以直接用async/await操作UI控件必然会出问题。下面是两种可行的解决方案:

方案一:使用Gtk的GLib.Timeout实现定时更新(推荐)

Gtk提供了GLib.Timeout.Add方法,它会在GUI线程中定时触发回调函数,完美符合你的需求。我们可以用这个方法来逐行插入文本:

void DisplayText(string FileName)
{
    // 读取所有行(如果文件很大,建议用StreamReader逐行读取,避免内存占用过高)
    var lines = File.ReadAllLines(FileName);
    var lineEnumerator = lines.GetEnumerator();

    // 每1000ms触发一次回调
    GLib.Timeout.Add(1000, () =>
    {
        if (!lineEnumerator.MoveNext())
        {
            // 所有行处理完毕,停止定时器
            lineEnumerator.Dispose();
            return false;
        }

        var currentLine = lineEnumerator.Current;
        var scoreBoard = currentLine.Split('\t');
        
        // 所有UI操作都在GUI线程执行,安全可靠
        TextIter ti = textview.Buffer.EndIter; // 改为从末尾插入,避免迭代器失效
        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");

        // 返回true继续触发定时器,返回false停止
        return true;
    });
}

方案二:手动将UI操作切回GUI线程

如果你坚持要用async/await,可以在await之后,用Gtk.Application.Invoke把UI操作包裹起来,强制在GUI线程执行:

async void DisplayText(string FileName)
{
    string[] temp = File.ReadAllLines(FileName);

    foreach (string line in temp)
    {
        var scoreBoard = line.Split('\t');
        await Task.Delay(1000);

        // 用Application.Invoke将UI操作调度到GUI线程
        Gtk.Application.Invoke((sender, e) =>
        {
            TextIter ti = textview.Buffer.EndIter; // 每次都获取最新的末尾迭代器
            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");
        });
    }
}

这里要注意:原来的TextIter是和文本缓冲区的状态绑定的,在await期间缓冲区可能已经被修改,所以直接复用原来的迭代器会导致插入位置错误,每次在UI操作前重新获取EndIter是更稳妥的做法。

额外建议

  • 如果你的文本文件很大,建议不要一次性用File.ReadAllLines读取所有内容,而是用StreamReader逐行读取,避免占用过多内存。
  • 始终记住Gtk的线程规则:任何UI控件的创建、修改、访问都必须在GUI线程执行,所有后台操作的结果都要通过Gtk.Application.Invoke或者GLib.Timeout/GLib.Idle调度到主线程处理。

内容的提问来源于stack exchange,提问作者Jihoo Byeon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 08:04:11