Invoke的用法

Windows Forms 的控件是线程绑定 的,只能在创建它的UI 线程上访问 / 修改。

  • 你在后台线程(比如 Task.RunThread、VisionPro 回调线程)里直接操作控件,会抛出 Cross-thread operation not valid 异常。
  • Invoke 的作用就是:把操作控件的代码,"切回" UI 线程执行,避免跨线程异常。

它本质是一个消息转发器:后台线程把一个委托扔给 UI 线程,UI 线程空闲时再执行这个委托里的代码。


二、Invoke 最标准的用法(直接抄就能用)

1. 最通用的写法(支持所有控件)

复制代码
// 场景:在后台线程里更新TextBox1的文本
private void UpdateUI()
{
    // 后台线程里的代码
    Task.Run(() =>
    {
        string result = "检测完成";
        int count = 10;

        // 关键:用Invoke包裹所有控件操作
        textBox1.Invoke(new Action(() =>
        {
            textBox1.Text = result;
            textBox2.Text = count.ToString();
            richTextBox1.AppendText($"{DateTime.Now:HH:mm:ss} 检测结束\n");
        }));
    });
}

2. 封装成通用方法(避免到处写重复代码)

如果你项目里很多地方都要更新 UI,推荐封装一个通用方法:

复制代码
public static void InvokeIfRequired(Control control, Action action)
{
    if (control.InvokeRequired)
    {
        control.Invoke(action);
    }
    else
    {
        action();
    }
}

// 使用示例:
InvokeIfRequired(textBox1, () =>
{
    textBox1.Text = "OK";
    richTextBox1.AppendText("检测OK\n");
});

这个方法的好处是:不管当前是不是在 UI 线程,都能安全执行控件更新代码,不用每次都判断 InvokeRequired


三、你项目里的场景:VisionPro 回调里用 Invoke

结合你之前的 SetResultAndSave 方法,给你一个适配 VisionPro 回调的标准写法

复制代码
void SetResultAndSave(int a)
{
    string res = blob.Outputs["Output"].Value.ToString().Trim();
    string srcFile = files[a];
    string fileName = Path.GetFileName(srcFile);

    if (res == "OK")
    {
        zhong++;
        liang++;
        File.Copy(srcFile, Path.Combine(okPath, fileName), true);
    }
    else if (res == "NG")
    {
        zhong++;
        File.Copy(srcFile, Path.Combine(nqPath, fileName), true);
    }

    // 不管OK还是NG,统一用Invoke更新UI
    Invoke(new Action(() =>
    {
        richTextBox1.AppendText($"{DateTime.Now:HH:mm:ss} 第{a}次检测{res}\r\n");
        textBox2.Text = liang.ToString(); // OK数
        textBox1.Text = zhong.ToString(); // 总数
    }));
}

⚠️ 注意:不要把 File.Copy、计数这些耗时操作写进 Invoke ,否则会阻塞 UI 线程,界面卡死。Invoke 里只放控件操作代码!


四、最常见的错误用法(你之前踩过的坑)

❌ 错误 1:把耗时操作写进 Invoke

复制代码
// 错误!Thread.Sleep、文件读取都在UI线程执行,直接卡死界面
Invoke(new Action(() =>
{
    Thread.Sleep(1000); // 卡死UI线程
    File.ReadAllText("test.txt"); // 阻塞UI
    textBox1.Text = "OK";
}));

✅ 正确:耗时操作放后台线程,只把控件更新写进 Invoke

复制代码
Task.Run(() =>
{
    Thread.Sleep(1000); // 后台线程延时,不卡UI
    string text = File.ReadAllText("test.txt");

    textBox1.Invoke(new Action(() =>
    {
        textBox1.Text = text; // 只更新控件
    }));
});

五、进阶用法:带返回值的 Invoke(Invoke<T>)

如果你需要从 UI 线程获取数据,可以用带返回值的 Invoke

复制代码
// 场景:后台线程里获取textBox1的文本
string GetTextBoxText()
{
    if (textBox1.InvokeRequired)
    {
        return (string)textBox1.Invoke(new Func<string>(() => textBox1.Text));
    }
    else
    {
        return textBox1.Text;
    }
}

// 使用:
string text = GetTextBoxText();

六、总结:Invoke 用法口诀

  • 后台线程做耗时,控件更新用 Invoke
  • Invoke 里只写控件操作,不写延时、文件读写、循环
  • 统一封装通用方法,避免重复代码
  • 循环变量要捕获,避免闭包陷阱
  • 先判断控件是否存在,再调用 Invoke
相关推荐
不正经学生2 小时前
C语言预处理详解:编译器真正动手之前的那些事
c语言·开发语言·算法·面试·bug
门思科技2 小时前
LoRaWAN 设备类别:Class A、B、C 对比与选型指南
c语言·开发语言·php
漂流瓶jz3 小时前
【AI】大模型本地部署与量化:Ollama、transformers、llama.cpp实践
人工智能·llm·ai编程
飞哥数智坊4 小时前
AI时代,我们到底该学什么、用什么,为什么还没提效?
人工智能
恋恋西风4 小时前
C++ 理解 std::thread 在单核和多核上的行为差异
开发语言·c++
Brilliantwxx5 小时前
【Linux】 进程(9)程序与进程地址空间(基础+进阶+面试题)
linux·运维·服务器·开发语言·c++
飞哥数智坊5 小时前
GPT 做架构,国内模型写代码:这条 AI 开发路线能跑通吗?
人工智能·ai编程
AI_AGENT_DEV_AI5 小时前
AI 智能体的开发与上线
人工智能
VL——MOESR5 小时前
【具身智能】VLA论文阅读随笔
论文阅读·人工智能·机器学习·具身智能·vla
BizzZ_5 小时前
C++(22)——类型转换和IO流
开发语言·c++