C#——invoke使用方法

本文介绍了使用.NET Framework WinForm时,在控件事件响应函数中改变控件状态的方法,包括为何不能直接修改以及如何通过Invoke方法正确实现状态更新。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在用.NET Framework框架的WinForm构建GUI程序界面时,如果要在控件的事件响应函数中改变控件的状态,例如:某个按钮上的文本原先叫“打开”,单击之后按钮上的文本显示“关闭”,初学者往往会想当然地这么写:

void ButtonOnClick(object sender,EventArgs e)
{
    button.Text="关闭";
}

这样的写法运行程序之后,可能会触发异常,异常信息大致是“不能从不是创建该控件的线程调用它”。注意这里是“可能”,并不一定会触发该种异常。造成这种异常的原因在于,控件是在主线程中创建的(比如this.Controls.Add(...);),进入控件的事件响应函数时,是在控件所在的线程,并不是主线程。在控件的事件响应函数中改变控件的状态,可能与主线程发生线程冲突。如果主线程正在重绘控件外观,此时在别的线程改变控件外观,就会造成画面混乱。不过这样的情况并不总会发生,如果主线程此时在重绘别的控件,就可能逃过一劫,这样的写法可以正常通过,没有触发异常。

正确的写法是在控件响应函数中调用控件的Invoke方法(其实如果大家以前用过C++ Builder的话,也会找到类似Invoke那样的激活到主线程的函数)。Invoke方法会顺着控件树向上搜索,直到找到创建控件的那个线程(通常是主线程),然后进入那个线程改变控件的外观,确保不发生线程冲突。正确写法的示例如下:

void ButtonOnClick(object sender,EventArgs e)
{
    button.Invoke(new EventHandler(delegate
    {
        button.Text="关闭";
    }));
}

Invoke方法需要创建一个委托。你可以事先写好函数和与之对应的委托。不过,若想直观地在Invoke方法调用的时候就看到具体的函数,而不是到别处搜寻的话,上面的示例代码是不错的选择。

这样的写法有一个烦人的地方:对不同的控件写法不同。对于TextBox,要TextBoxObject.Invoke,对于Label,又要LabelObject.Invoke。有没有统一一点的写法呢?

主窗口类本身也有Invoke方法。如果你不想对不同的控件写法不一样,可以全部用this.Invoke:

void ButtonOnClick(object sender,EventArgs e)
{
    this.Invoke(new EventHandler(delegate
    {
        button.Text="关闭";
    }));
}

在C# 3.0及以后的版本中有了Lamda表达式,像上面这种匿名委托有了更简洁的写法。.NET Framework 3.5及以后版本更能用Action封装方法。例如以下写法可以看上去非常简洁:

void ButtonOnClick(object sender,EventArgs e)
{
    this.Invoke(new Action(()=>
    {
        button.Text="关闭";
    }));
}

以上写法往往充斥着WinForm构建的程序。

在微软新一代的界面开发技术WPF中,由于界面呈现和业务逻辑原生态地分开在两个线程中,所以控件的事件响应函数就不必Invoke了。但是,如果手动开辟一个新线程,那么在这个新线程中改变控件的外观,则还是要Invoke的。

### C# Invoke 方法详解 #### 调用委托的方法 `Invoke` 是用于同步调用委托的一个重要方法。当需要在一个线程上调用另一个线程上的方法时,此功能特别有用。例如,在Windows Forms应用程序中更新UI控件通常需要通过 `Invoke` 来实现跨线程操作。 下面是一个简单的例子展示了如何利用 `Action` 委托以及 `Invoke` 方法来改变按钮的文字属性: ```csharp void ButtonOnClick(object sender, EventArgs e) { this.Invoke(new Action(() => { button.Text = "关闭"; })); } ``` 这段代码创建了一个新的 `Action` 对象并立即执行它,从而安全地修改了 UI 控件的内容[^1]。 #### 异步调用 BeginInvoke 和 EndInvoke 对于希望以非阻塞的方式运行某些逻辑的情况,则可以考虑使用 `BeginInvoke` 及其对应的完成通知机制——`EndInvoke`。这允许程序继续处理其他任务而不必等待被调用的操作结束。 假设有一个耗时较长的任务想要异步执行,可以通过如下方式设置: ```csharp // 定义一个接受整数参数返回字符串结果的委托 public delegate string LongRunningTaskDelegate(int input); private void StartLongTask() { var taskDelegate = new LongRunningTaskDelegate(LongRunningMethod); IAsyncResult result = taskDelegate.BeginInvoke(42, asyncResult => { try { // 当异步操作完成后获取最终的结果 string outcome = taskDelegate.EndInvoke(asyncResult); MessageBox.Show(outcome); } catch (Exception ex) { MessageBox.Show(ex.Message); } }, null); } ``` 这里展示的是启动长时间运行的方法,并在其结束后显示消息框告知用户[^2]。 #### P/Invoke 的应用实例 除了管理多线程间的通信外,`Invoke` 还可以在平台调用服务(P/Invoke)场景下发挥作用。比如要从托管环境访问未托管库中的函数,就需要先声明合适的委托类型作为接口桥梁。 以下是连接到外部C++动态链接库(DLL),并通过注册回调函数来进行数据交换的例子: ```csharp using System; using System.Runtime.InteropServices; class Program { // 定义回调函数的委托 public delegate void Callback(int value); [DllImport("MyCppLibrary.dll", CallingConvention = CallingConvention.Cdecl)] public static extern void RegisterCallback(Callback cb); static void Main(string[] args) { // 实例化回调函数 Callback callback = new Callback(PrintValue); // 注册回调给DLL内部使用 RegisterCallback(callback); } static void PrintValue(int value) { Console.WriteLine($"Callback value: {value}"); } } ``` 上述片段说明了怎样借助于 `delegate` 结合 `Invoke` 技术有效地实现了不同编程语言之间的协作工作流[^3]。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值