C#Invoke和BeginInvoke的用法转载至微雨夏凉

这篇具有很好参考价值的文章主要介绍了C#Invoke和BeginInvoke的用法转载至微雨夏凉。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

C#.Net教程
1、Invoke到底是什么?
Invoke的本质只是一个方法,方法一定是要通过对象来调用的。

一般来说,Invoke其实用法只有两种情况:

Control的Invoke
Delegate的Invoke
也就是说,Invoke前面要么是一个控件,要么是一个委托对象。

2、什么时候用Invoke
2.1 Control的Invoke
Control的Invoke一般用于解决跨线程访问的问题,比如你想操作一个按钮button,你就要用button.Invoke,你想操作一个文本label,你就要用label.Invoke,但是大家会发现很麻烦,如果我想既操作button,又操作label,能不能写在一起呢?当然可以。
我们知道,主窗体是一个Form,Form自然也是继承Control的,所以Form也有Invoke的方法,可以直接调用Form.Invoke,这就是我们常见的this.Invoke。
这就是为什么有的Invoke前面啥都没有的问题,其实前面是this,只不过省略了。

2.2 Delegate的Invoke
Delegate的Invoke其实就是从线程池中调用委托方法执行,Invoke是同步的方式,会卡住调用它的UI线程。很抽象吧。

3、实验
我们来做个简单的实验。

3.1 新建一个From ,来个Button,我想实现的功能是,点击Button时, Button 变成Disable,并开始显示计算1到8(每隔1秒加1),加到8后,跳出循环,然后把Button Enable,很简单吧。

小事情拉,于是开始行动,很快就搞定了,代码如下:

namespace InvokeTest1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

    /// <summary>
    /// Butten 点击事件
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void btnAddFunction_Click_1(object sender, EventArgs e)
    {
        btnAddFunction.Enabled = false;
        for (int i = 1; i < 8; i++)
        {
            btnAddFunction.Text = i.ToString();
            Thread.Sleep(1000);
        }
        btnAddFunction.Text = "点击开始运行";
        btnAddFunction.Enabled = true;
    }
}

}
开始运行,点击Button,控件是变成Disable,但是没有实现计数呀,而是一直就这样停止8秒,就像被卡住了一样。

8秒后,Button控件直接变成Enable。没有1—8出现,逻辑不对啊,怎么回事?????

原因:直接主线程休眠是达不到效果的,此时桌面还处于假死状态,更新不了text值。代码放在了UI线程执行,阻塞了UI的显示,所以中间的结果你看不到。

3.2 找到了原因,那就好办了,既然代码放在了UI线程,那就新建个线程,在那里面更新UI控件好了。信心满满的开始行动。

namespace InvokeTest1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

    /// <summary>
    /// Butten 点击事件
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void btnAddFunction_Click_1(object sender, EventArgs e)
    {
        //启动一个线程,在这个线程里更新Button值
        new Thread(ThreadTask).Start();
    }

    /// <summary>
    /// 线程函数
    /// </summary>
    public void ThreadTask()
    {
        btnAddFunction.Enabled = false;
        for (int i = 1; i < 8; i++)
        {
            btnAddFunction.Text = i.ToString();
            Thread.Sleep(1000);
        }
        btnAddFunction.Text = "点击开始运行";
        btnAddFunction.Enabled = true;
    }

}

}
真实现了数字的更新。

功能是实现了,但没有达到我的目的,主角都没有登场,就谢幕了啊。网上查看了资料,说,这种方法,是不稳定的,特别是主窗口控件比较多的时候,很容易出错,造成画面混乱。为什么呢?因为控件是在主线程中创建的(比如this.Controls.Add(…)😉,进入控件的事件响应函数时,是在控件所在的线程,并不是主线程。在控件的事件响应函数中改变控件的状态,可能与主线程发生线程冲突。如果主线程正在重绘控件外观,此时在别的线程改变控件外观,就会造成画面混乱。

4、主角出场
4.1 C#的委托机制,一般有下面几种方式。
//第一种
btnAddFunction.Invoke(new EventHandler(delegate{button1.Text = “关闭”;}));
//第二种
this.Invoke(new EventHandler(delegate{button1.Text = “关闭”;}));
//第三种 网上说自C# 3.0开始就有了
this.Invoke(new Action(() =>{ button1.Text = “关闭”;}));
现在应用最多就是第3种了,因为现在版本基本上都是4.0以上了,所以线程函数改为如下的:

namespace InvokeTest1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

    /// <summary>
    /// Butten 点击事件
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void btnAddFunction_Click_1(object sender, EventArgs e)
    {
        //启动一个线程,在这个线程里更新Button值
        new Thread(ThreadTask).Start();
    }

    /// <summary>
    /// 线程函数
    /// </summary>
    public void ThreadTask()
    {
        //首先将button对象禁用
        this.Invoke(new Action(() =>
        {
            btnAddFunction.Enabled = false;
        }));
        for (int i = 0; i < 10; i++)
        {
            this.Invoke(new Action(() =>
            {
                btnAddFunction.Text = i.ToString();
            }));
            Thread.Sleep(1000);
        }
        //虽然不是循环内,请不要忘记,你的调用依然在辅助线程中,所以,还是需要invoke的。
        this.Invoke(new Action(() =>
        {
            btnAddFunction.Text = "点击开始运行";
            btnAddFunction.Enabled = true;
        }));

    }

}

}

4.2 Control的Invoke标准用法
其实,对于Control的Invoke,更标准的用法是先加判断,再调用。

if (this.lbl_Value.InvokeRequired)
{
this.lbl_Value.Invoke(new Action(() =>
{
this.lbl_Value.Text = “Invoke功能测试”;
}));
}
else
{
this.lbl_Value.Text = “Invoke测试失效”;
}
InvokeRequired是Control的一个属性,官方解释为:
获取一个值,该值指示调用方在对控件进行方法调用时是否必须调用 Invoke 方法,因为调用方位于创建控件所在的线程以外的线程中。如果控件的 Handle 是在与调用线程不同的线程上创建的(说明您必须通过 Invoke 方法对控件进行调用),则为 true;否则为 false。

简单来说,就是如果通过多线程去操作这个控件,那么这个属性则为True,否则为False。

实例化一个 this invoke的用法


using System.Threading;

public delegate void MyInvoke(string str);//invoke方法创建委托
private void btnStartThread_Click(object sender, EventArgs e)
{
Thread thread = new Thread(new ThreadStart(DoWord));
thread.Start();
}
public void DoWord()
{
MyInvoke mi = new MyInvoke(SetTxt);//实例化一个委托,并且指定委托方法
BeginInvoke(mi,new object[]{“abc”}); //调用invoke方法
}

public void SetTxt(string str)//委托对应的方法
{
txtReceive.Text += str;
}

4.3 Delegate的Invoke 标准写法
对于Delegate的Invoke,我们一般判断这个方法之前,也是做个判断,判断这个委托对象是否为Null,所以更标准的写法如下:

DelegateInvokeFun testDelegate = new DelegateInvokeFun(DelegateInvokeMethod);
testDelegate?.Invoke();
5、 Invoke和BeginInvoke
Control.Invoke 和 Control.BeginInvoke

5.1 测试实例1 利用 控件中的Invoke 和 BeginInvoke 方法
作用1:在线程中执行访问和修改UI内容

作用2:Invoke可以阻塞线程,等待UI操作返回

作用3:BeginInvoke不阻塞线程,后台刷新UI,提高程序的流畅性

public partial class frmMain : Form
{
public frmMain ()
{
InitializeComponent();
}

  public void ThreadRun()
  {
      while (true)
      {
         Thread.Sleep(1);

            this.Invoke(new Action(() => 
            { 
                MessageBox.Show("Invoke的方法");
                // 在该this(Form)控件的线程中执行Action中的委托
                // 你可以在此获取UI变量 或者 改变UI变量
                // 但是 ThreadRun线程会被阻塞 等待 Action 执行完成
            }));

            this.BeginInvoke(new Action(() =>
            {
                MessageBox.Show("BeginInvoke的方法");
                // 在该this(Form)控件的线程中执行Action中的委托
                // 你可以在此获取UI变量 或者 改变UI变量
                // 但是 ThreadRun线程不会被阻塞继续向下执行
            }));
      }
  }

}

5.2 测试实例2 Action 等delegate 中Invoke和BeginInvoke的作用
作用1.Invoke在当前函数中立即执行,相当于直接调用该Action所注册的所有函数,阻塞当前函数帧

作用2.BeginInvoke在当前函数帧中开辟这个线程去执行Action所注册的函数,不阻塞当前函数帧

作用3.BeginInvoke 中有2额外两个参数 (arg1 回调委托,任意参数)用于beginInvoke完成后,执行该某些动作,

注意:结束完回调的函数 所在线程 为调用beginInvoke 的线程

private void Form1_Load(object sender, EventArgs e)
{
// 在当前函数所在线程中执行,当前函数线程阻塞
ShowMsg += (str) => { Debug.WriteLine(str); };
ShowMsg.Invoke(“This is Invoke test!”);

        // 开辟一个线程执行 BeginInvoke Test,当前函数线程不阻塞
        ShowMsg.BeginInvoke(" BeginInvoke Test", null, null);


        // AsyncCallback 回调委托
        // IAsyncResult 异步执行的结果,可以自我继承,添加自定义参数 用于显示异步执行的状态
        // index(object类型) 外部传参,存储在IAsyncResult.AsyncState中
        AsyncCallback asyncCallback = ar => { Debug.WriteLine($"beginInvoke 执行完成 回调{ar.AsyncState}"); };
        int index = 0;
        ShowMsg.BeginInvoke(" BeginInvoke Test", asyncCallback, index);
    }

6 应用 测速下载多个文件时耗用的时间 - 异步编程实现
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace InvokeDemo
{
public partial class 异步编程 : Form
{
public delegate string delegateObj(string paht);
public 异步编程()
{
InitializeComponent();
delobj = new delegateObj(copyFile);
}
delegateObj delobj=null;
///
/// 同步拷贝文件,并输出文件名称
///
///
///
private void button1_Click(object sender, EventArgs e)
{
String[] paths = Directory.GetFiles(@“F:\迅雷下载”);
for (int i = 0; i < paths.Length; i++)
{
this.listBox1.Items.Add(copyFile(paths[i]));
}
}
/// 异步拷贝文件,并输出文件名
///
///
///
///
private void button2_Click(object sender, EventArgs e)
{文章来源地址https://www.toymoban.com/news/detail-781737.html

        String[] paths = Directory.GetFiles(@"F:\迅雷下载");
        for (int i = 0; i < paths.Length; i++)
        {
            delobj.BeginInvoke(paths[i], callback, Path.GetFileName(paths[i]));//最后一个参数是回调状态  ,如果不需要回调函数的话,直接null,这样就不需要调用EndInvoke,单纯的BeginInvoke即可
        }
    }
    public void callback(IAsyncResult result)
    {
        string filename = "";
        //if (this.listBox1.InvokeRequired)
        //{
        //    filename = this.listBox1.EndInvoke(result).ToString();
        //    this.listBox1.Items.Add(filename + "复制成功");
        //    this.listBox2.Items.Add(result.AsyncState.ToString() + "复制成功");
            
        //}
        filename = delobj.EndInvoke(result);
        //this.listBox1.Items.Add(filename + "复制成功");
        //this.listBox2.Items.Add(result.AsyncState.ToString() + "复制成功");

        Console.WriteLine(result.AsyncState.ToString() + "复制成功");
    }
    /// <summary>
    /// 拷贝文件用
    /// </summary>
    /// <param name="filename"></param>
    public string copyFile(string filename)
    {
        if (filename.EndsWith("desktop.ini"))
            return "desktop.ini";
        if (File.Exists(@"F:\迅雷下载2\" + Path.GetFileName(filename)))
        {
            File.Delete(@"F:\迅雷下载2\" + Path.GetFileName(filename));
        }
        File.Copy(filename, @"F:\迅雷下载2\" + Path.GetFileName(filename));
        return Path.GetFileName(filename);
    }
}

到了这里,关于C#Invoke和BeginInvoke的用法转载至微雨夏凉的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【转载】L2Dwidget.js网页二次元看板娘的用法

    最近新建了博客,https://yellowgg.cn,许久不更新的博客园想引个流,可以关注一波嗷。 发现某些blog网站左下方或者右下方出现的二次元卡通人物或萌萌阿猫,除了萌,还可以监听鼠标的行为,产生互动的现象。 L2Dwidget.min.js的源码:https://github.com/xiazeyu/live2d-widget.js.git 起初我

    2024年01月24日
    浏览(30)
  • pyqt中有connect用法的控件

    在PyQt中,以下控件可以使用connect方法进行信号与槽的连接: QPushButton:连接点击按钮的信号clicked QAction:连接菜单项或工具栏按钮的信号triggered QComboBox:连接选择项发生改变的信号currentIndexChanged QLineEdit:连接文本内容改变的信号textChanged QRadioButton:连接单选按钮状态切换

    2024年02月16日
    浏览(27)
  • Qt QTableWidget表格控件的用法(非常详细)

    QTableWidget 是 Qt 提供的一种表格控件(如图 1 所示),类似于我们经常使用的 Excel 表格,可以将数据以表格的方式展示给用户。 整个 QTableWidget 表格可以分为 3 个区域: 区域 ① 和 ② 都是表头,区域 ① 设置每一行的表头,区域 ② 设置每一列的表头。我们可以自定义两个区

    2024年01月20日
    浏览(40)
  • MFC表格控件CListCtrl的改造及用法

      简单描述MFC的表格控件使用方法。Qt适用习惯了以后MFC用的比较别扭,因此记录一下以备后续复制代码使用。由于MFC原生的CListCtrl比较局限,比如无法改变表格的背景色、文字颜色等设定,因此先对CListCtrl类进行重写,以便满足要求。   初始化表格界面效果

    2024年02月15日
    浏览(40)
  • Qt comboBox控件的三种基本用法

    1、用法一: 一般用到控件comboBox的基本用法是获取当前的值: 1.currentIndex(); 获取当前comBox的索引,是int类型的值。 2.currentText(); 获取当前comBox的文本,是QString类型。 2、用法二: 可以通过以下两种方式来通过切换comobox的值来执行一些指令: 1、通过首先在界面上拖入控件

    2024年02月12日
    浏览(43)
  • 【WPF应用35】深度解析WPF中的TreeView控件:功能、用法、特性与最佳实践

    WPF(Windows Presentation Foundation)是微软推出的一个用于构建桌面应用程序的图形子系统。在WPF中,TreeView是一种常用的树形控件,用于显示层次结构的数据显示。本文将详细介绍WPF中的TreeView控件,并提供一个简单的示例。 TreeView控件用于显示一个层次结构的列表,通常用于显

    2024年04月10日
    浏览(80)
  • 面向对象编程 实验三 sduwh 子窗口与控件的基本用法、资源的使用 参考实验报告1

    源自网络收集,仅供参考 实验三收集到两份完整报告,这是其一,另一份见本专栏下一篇文章。 《面向对象程序设计》 实验三 实验题目:子窗口与控件的基本用法、资源的使用 整体目的:理解、窗口之间的消息传送、调用栈;掌握光标、图标、菜单的制作和使用方式;掌

    2024年02月07日
    浏览(40)
  • C# 中this.Invoke(delegate)与delegate.Invoke()区别

    在多线程运行中,想要更改UI控件的属性。一直认为两种方式是一样的,以前也是混着写的,这次翻车了。 这个是 在拥有控件的基础窗口句柄的线程上,用指定的自变量列表执行指定委托。 ,也就是说通知UI线程处理事件,可跨线程。 这个是 执行委托,运行在创建委托的线

    2023年04月08日
    浏览(40)
  • C# 中的Invoke 方法

    问题的引发: ?.Invoke()是什么意思。 答案如下: 同时又想到UI线程那块,也有invoke,区别是什么?故此总结如下: 在 C# 中,Invoke 方法可用于委托调用、控制 UI 线程操作和反射调用等多种情况。具体使用方法取决于上下文和所涉及的类型。 1、委托的 Invoke 方法:委托类型具有

    2024年02月13日
    浏览(38)
  • C# 中的Invoke方法

    Invoke 是委托类型的实例方法,用于调用委托所引用的方法。委托是一种类型,它允许我们将方法作为参数传递并存储在字段或属性中。当委托实例被调用时,它会调用与之关联的方法。可以使用 += 运算符将一个方法添加到委托中,使用 -= 运算符将其从委托中删除。 在具体使

    2024年02月13日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包