c# Invoke 与BeginInvoke 的用法(通俗易懂,最全)

**

c# Invoke 与BeginInvoke 的用法

**
最近在学习线程时,发现当我创建的线程需要访问UI界面的时,会发生异常,原因是我在跨线程调用主线程的控件,因此windows GUI编程有一个规则,就是只能通过创建控件的线程来操作控件的数据,否则就可能产生不可预料的结果。
有时候,我们不得不跨线程调用主界面的控件来进行操作,所以为了方便的解决问题,.net为我们提供了Invoke 与beginInvoke
Invoke 与begininvoke区别在于,invoke会阻塞当前线程,直到invoke调用结束,才会继续执行下去,而begininvoke 则可以异步进行调用,也就是该方法封送完毕后马上返回,不会等待委托方法的执行结束,调用者线程将不会被阻塞。但是调用者也可以使用EndInvoke方法或者其它类似WaitHandle机制等待异步操作的完成。
先讲下Invoke:
// 定义委托函数 ,委托函数与被委托函数必须要有相同返回值和参数列表
public delegate void myDelegate(string str);
public void _invoke_myDelegate(String str)
{
// invokeRequired 获取一个bool值 判断调用控件是否必须要调用invoke方法
// 如果调用对象在其他线程,则返回true,否则返回false
if (this.InvokeRequired)
{
/* Action action = new Action(_invoke_myDelegate);*/
// 确定调用对象在其他线程 则调用invoke函数 它会返回到拥有这个控件的线程上
// 利用委托函数,再次调用被委托函数,str为委托函数的参数列表
this.Invoke(new myDelegate(_invoke_myDelegate), str);
}
// 当委托函数执行时, 此时已经回到控件线程,可以直接调用控件label
label1.Text = str;
}
这里Invoke 必须等委托函数调用完成之后,才会执行后面操作,那么当我们的委托函数执行的是一个非常耗时的操作
这样线程就会被阻塞,造成用户界面卡顿的情况,所以,为了解决invoke同步的问题,还有一种就是beginInvoke
BeginInvoke
BeginInvoke方法触发你的异步方法,它和你想要执行的异步方法有相同的参数。另外还有两个可选参数,第一个是AsyncCallback委托是异步完成的回调方法。第二个是用户自定义对象,该对象将传递到回调方法中。BeginInvoke立即返回并且不等待完成异步的调用(继续执行该下面的代码,不需要等待)。BeginInvoke返回IAsyncResult接口,可用于检测异步调用的过程。
通过EndInvoke方法检测异步调用的结果。如果异步调用尚未完成,EndInvoke将阻塞调用线程,直到它完成。EndInvoke参数包括out和ref参数。
不管怎么么样,调用了beginInvoke ,就必须调用endInvoke 结束异步,
那我们怎么才能知道什么时候异步结束呢,常见四种方法:
1.做一些其他操作,然后调用EndInvoke方法阻塞线程直到该方法完成。
2.使用IAsyncResult.AsyncWaitHandle属性,使用它的WaitOne方法阻塞线程直到收到WaitHandle信号,然后调用EndInvoke。
3.检查BeginInvoke返回值IAsyncResult的状态来决定方法是否完成,然后调用EndInvoke方法。
4.通过在BeginInvoke方法中传递该委托,在回调方法中调用该委托的EndInvoke方法。

AsyncMethodCaller caller = new AsyncMethodCaller(TestMethodAsync); // caller 为委托函数
int threadid = 0;
//开启异步操作
IAsyncResult result = caller.BeginInvoke(1000, out threadid, null, null);
for (int i = 0; i < 10; i++)
{
Console.WriteLine(“其它业务” + i.ToString());
}
//调用EndInvoke,等待异步执行完成
Console.WriteLine(“等待异步方法TestMethodAsync执行完成”);
//等待异步执行完毕信号
//result.AsyncWaitHandle.WaitOne();
//Console.WriteLine(“收到WaitHandle信号”);
//通过循环不停的检查异步运行状态
while (result.IsCompleted==false)
{
Thread.Sleep(100);
Console.WriteLine(“异步方法,running…”);
}
//异步结束,拿到运行结果
string res = caller.EndInvoke(out threadid, result);
//显示关闭句柄
result.AsyncWaitHandle.Close();
Console.WriteLine(“关闭了WaitHandle句柄”);
static string TestMethodAsync(int callDuration, out int threadId)
{
Stopwatch sw = new Stopwatch();
sw.Start();
Console.WriteLine(“异步TestMethodAsync开始”);
for (int i = 0; i < 5; i++)
{ // 模拟耗时操作
Thread.Sleep(callDuration);
Console.WriteLine(“TestMethodAsync:” + i.ToString());
}
sw.Stop();
threadId = Thread.CurrentThread.ManagedThreadId;
return string.Format(“耗时{0}ms.”, sw.ElapsedMilliseconds.ToString());
}

**

补充示例说明:

**
在多线程编程中,我们经常要在工作线程中去更新界面显示,而在多线程中直接调用界面控件的方法是错误的做法,Invoke 和 BeginInvoke 就是为了解决这个问题而出现的,使你在多线程中安全的更新界面显示。
正确的做法是将工作线程中涉及更新界面的代码封装为一个方法,通过 Invoke 或者 BeginInvoke 去调用,两者的区别就是一个导致工作线程等待,而另外一个则不会。
而所谓的“一面响应操作,一面添加节点”永远只能是相对的,使 UI 线程的负担不至于太大而已,因为界面的正确更新始终要通过 UI 线程去做,我们要做的事情是在工作线程中包揽大部分的运算,而将对纯粹的界面更新放到 UI 线程中去做,这样也就达到了减轻 UI 线程负担的目的了。
举个简单例子说明下使用方法,比如你在启动一个线程,在线程的方法中想更新窗体中的一个TextBox…
using System.Threading;

//启动一个线程
Thread thread=new Thread(new ThreadStart(DoWork));
thread.Start();

//线程方法
private void DoWork()
{
  this.TextBox1.Text=“我是一个文本框”;
}
如果你像上面操作,在VS2005或2008里是会有异常的…
正确的做法是用Invoke\BeginInvoke
using System.Threading;
namespace test
{
  public partial class Form1 : Form
  {
    public delegate void MyInvoke(string str1,string str2);
    public Form1()
    {
      InitializeComponent();

}
    public void DoWork()
    {
      MyInvoke mi = new MyInvoke(UpdateForm);
      this.BeginInvoke(mi, new Object[] {“我是文本框”,“haha”});
    }
    public void UpdateForm(string param1,string parm2)
    {
      this.textBox1.Text = param1+parm2;
    }
    private void button1_Click(object sender, EventArgs e)
    {
      Thread thread = new Thread(new ThreadStart(DoWork));
      thread.Start();
    }
  }
}
注意代理的使用!
后面再次补充
  在 WinForm开发过程中经常会用到线程,有时候还往往需要在线程中访问线程外的控件,比如:设置textbox的Text属性等等。如果直接设置程序必 定会报出:从不是创建控件的线程访问它,这个异常。通常我们可以采用两种方法来解决。一是通过设置control的属性。二是通过delegate,而通 过delegate也有两种方式,一种是常用的方式,另一种就是匿名方式。下面分别加以说明.
  首先,通过设置control的一个属性值为false.我们可以在Form_Load方法中添加:Control.CheckForIllegalCrossThreadCalls=false;来解决。设置为false表示不对错误线程的调用进行捕获。这样在线程中对textbox的Text属性进行设置时就不会再报错了。
  其次,通过delegate的方法来解决。
普通的委托方法例如:
delegate void SafeSetText(string strMsg);
private void SetText(string strMsg)
{
if(textbox1.InvokeRequired)
{
SafeSetText objSet=new SafeSetText(SetText);
textbox1.Invoke(objSet,new object[]{strMsg});
}
else
{
  textbox1.Text=strMsg;
}
}
在线程内需要设置textbox的值时调用SetText方法既可。我们还可以采用另一种委托的方式来实现,那就是匿名代理,例如:
delegate void SafeSetText(string strMsg);
private void SetText2(string strMsg)
{
  SafeSetText objSet = delegate(string str)
{
textBox1.Text = str;
}
textBox1.Invoke(objSet,new object[]{strMsg});
}
这样同样可以实现。
个人觉得还是采用代理好些。
在C# 3.0及以后的版本中有了Lamda表达式,像上面这种匿名委托有了更简洁的写法。.NET Framework 3.5及以后版本更能用Action封装方法。例如以下写法可以看上去非常简洁:
void ButtonOnClick(object sender,EventArgs e)
{
this.Invoke(new Action(()=>
{
button.Text=“关闭”;
}));
}
最新:
Invoke(() =>
{
button.Text=“关闭”;
});
以上就是C# Invoke,begininvoke的用法详解的详细内容,更多关于C# Invoke,begininvoke的资料请关注脚本之家其它相关文章!

private void DisplayError(string message)
{
if (this.InvokeRequired)
this.BeginInvoke(new UpdateString(DisplayError), new object[] { message });
else
{
MessageBox.Show(this, message, “QuickBuild Client Sample”);
StopClient();
}
}

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

We唯有自己强大

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值