- 浏览: 470977 次
- 性别:
- 来自: 北京
文章分类
最新评论
-
chexiazi:
一样的xml代码 报这个错 <ns1:XMLFault ...
CXF 通过用户名和密码进行验证 -
di1984HIT:
谢谢啊 ~~~
通过JavaCompiler进行编译java文件(转载) -
aa00aa00:
'%${userName}%' 这种是可以的,是可以模糊查询的 ...
mybatis 模糊查询 -
一棵杨柳的地盘:
我把你的代码不了一遍 但是汇报错首先:static { ...
CXF 通过用户名和密码进行验证 -
shangmin1990:
转 IntelliJ IDEA 编辑器生成 Hibernate 实体映射文件
在Invoke或者BeginInvoke的使用中无一例外地使用了委托Delegate
一、为什么Control类提供了Invoke和BeginInvoke机制?
关于这个问题的最主要的原因已经是dotnet程序员众所周知的,我在此费点笔墨再次记录到自己的日志,以便日后提醒一下自己。
1、windows程序消息机制
Windows GUI程序是基于消息机制的,有个主线程维护着一个消息泵。这个消息泵让windows程序生生不息。
Windows GUI程序的消息循环
Windows程序有个消息队列,窗体上的所有消息是这个队列里面消息的最主要来源。这里的while循环使用了GetMessage()这个方法,这是个阻塞方法,也就是队列为空时方法就会被阻塞,从而这个while循环停止运动,这避免了一个程序把cpu无缘无故地耗尽,让其它程序难以得到响应。当然在某些需要cpu最大限度运动的程序里面就可以使用另外的方法,例如某些3d游戏或者及时战略游戏中,一般会使用PeekMessage()这个方法,它不会被windows阻塞,从而保证整个游戏的流畅和比较高的帧速。
这个主线程维护着整个窗体以及上面的子控件。当它得到一个消息,就会调用DispatchMessage方法派遣消息,这会引起对窗体上的窗口过程的调用。窗口过程里面当然是程序员提供的窗体数据更新代码和其它代码。
2、dotnet里面的消息循环
public static void Main(string[] args)
{
Form f = new Form();
Application.Run(f);
}
Dotnet窗体程序封装了上述的while循环,这个循环就是通过Application.Run方法启动的。
3、线程外操作GUI控件的问题
如果从另外一个线程操作windows窗体上的控件,就会和主线程产生竞争,造成不可预料的结果,甚至死锁。因此windows GUI编程有一个规则,就是只能通过创建控件的线程来操作控件的数据,否则就可能产生不可预料的结果。
因此,dotnet里面,为了方便地解决这些问题,Control类实现了ISynchronizeInvoke接口,提供了Invoke和BeginInvoke方法来提供让其它线程更新GUI界面控件的机制。
public interface ISynchronizeInvoke
{
[HostProtection(SecurityAction.LinkDemand, Synchronization=true, ExternalThreading=true)]
IAsyncResult BeginInvoke(Delegate method, object[] args);
object EndInvoke(IAsyncResult result);
object Invoke(Delegate method, object[] args);
bool InvokeRequired { get; }
}
}
如果从线程外操作windows窗体控件,那么就需要使用Invoke或者BeginInvoke方法,通过一个委托把调用封送到控件所属的线程上执行。
二、消息机制---线程间和进程间通信机制
1、window消息发送
Windows消息机制是windows平台上的线程或者进程间通信机制之一。Windows消息值其实就是定义的一个数据结构,最重要的是消息的类型,它就是一个整数;然后就是消息的参数。消息的参数可以表示很多东西。
Windows提供了一些api用来向一个线程的消息队列发送消息。因此,一个线程可以向另一个线程的消息队列发送消息从而告诉对方做什么,这样就完成了线程间的通信。有些api发送消息需要一个窗口句柄,这种函数可以把消息发送到指定窗口的主线程消息队列;而有些则可以直接通过线程句柄,把消息发送到该线程消息队列中。
用消息机制通信
SendMessage是windows api,用来把一个消息发送到一个窗口的消息队列。这个方法是个阻塞方法,也就是操作系统会确保消息的确发送到目的消息队列,并且该消息被处理完毕以后,该函数才返回。返回之前,调用者将会被暂时阻塞。
PostMessage也是一个用来发送消息到窗口消息队列的api函数,但这个方法是非阻塞的。也就是它会马上返回,而不管消息是否真的发送到目的地,也就是调用者不会被阻塞。
2、Invoke and BeginInvoke
Invoke or BeginInvoke
Invoke或者BeginInvoke方法都需要一个委托对象作为参数。委托类似于回调函数的地址,因此调用者通过这两个方法就可以把需要调用的函数地址封送给界面线程。这些方法里面如果包含了更改控件状态的代码,那么由于最终执行这个方法的是界面线程,从而避免了竞争条件,避免了不可预料的问题。如果其它线程直接操作界面线程所属的控件,那么将会产生竞争条件,造成不可预料的结果。
使用Invoke完成一个委托方法的封送,就类似于使用SendMessage方法来给界面线程发送消息,是一个同步方法。也就是说在Invoke封送的方法被执行完毕前,Invoke方法不会返回,从而调用者线程将被阻塞。
使用BeginInvoke方法封送一个委托方法,类似于使用PostMessage进行通信,这是一个异步方法。也就是该方法封送完毕后马上返回,不会等待委托方法的执行结束,调用者线程将不会被阻塞。但是调用者也可以使用EndInvoke方法或者其它类似WaitHandle机制等待异步操作的完成。
但是在内部实现上,Invoke和BeginInvoke都是用了PostMessage方法,从而避免了SendMessage带来的问题。而Invoke方法的同步阻塞是靠WaitHandle机制来完成的。
3、使用场合问题
如果你的后台线程在更新一个UI控件的状态后不需要等待,而是要继续往下处理,那么你就应该使用BeginInvoke来进行异步处理。
如果你的后台线程需要操作UI控件,并且需要等到该操作执行完毕才能继续执行,那么你就应该使用Invoke。否则,在后台线程和主截面线程共享某些状态数据的情况下,如果不同步调用,而是各自继续执行的话,可能会造成执行序列上的问题,虽然不发生死锁,但是会出现不可预料的显示结果或者数据处理错误。
可以看到ISynchronizeInvoke有一个属性,InvokeRequired。这个属性就是用来在编程的时候确定,一个对象访问UI控件的时候是否需要使用Invoke或者BeginInvoke来进行封送。如果不需要那么就可以直接更新。在调用者对象和UI对象同属一个线程的时候这个属性返回false。在后面的代码分析中我们可以看到,Control类对这一属性的实现就是在判断调用者和控件是否属于同一个线程的。
三、Delegate.BeginInvoke
通过一个委托来进行同步方法的异步调用,也是.net提供的异步调用机制之一。但是Delegate.BeginInvoke方法是从ThreadPool取出一个线程来执行这个方法,以获得异步执行效果的。也就是说,如果采用这种方式提交多个异步委托,那么这些调用的顺序无法得到保证。而且由于是使用线程池里面的线程来完成任务,使用频繁,会对系统的性能造成影响。
Delegate.BeginInvoke也是讲一个委托方法封送到其它线程,从而通过异步机制执行一个方法。调用者线程则可以在完成封送以后去继续它的工作。但是这个方法封送到的最终执行线程是运行库从ThreadPool里面选取的一个线程。
这里需要纠正一个误区,那就是Control类上的异步调用BeginInvoke并没有开辟新的线程完成委托任务,而是让界面控件的所属线程完成委托任务的。看来异步操作就是开辟新线程的说法不一定准确。
四、用Reflector察看一些相关代码
1、Control.BeginInvoke and Control.Invoke
public IAsyncResult BeginInvoke(Delegate method, params object[] args)
{
using (new MultithreadSafeCallScope())
{
return (IAsyncResult) this.FindMarshalingControl().MarshaledInvoke(this, method, args, false);
}
}
public object Invoke(Delegate method, params object[] args)
{
using (new MultithreadSafeCallScope())
{
return this.FindMarshalingControl().MarshaledInvoke(this, method, args, true);
}
}
我们可以看到Invoke和BeginInvoke方法使用了同样的实现,只是MarshaledInvoke方法的最后一个参数值不一样。
2、MarshaledInvoke
private object MarshaledInvoke(Control caller, Delegate method, object[] args, bool synchronous)
{
int num;
if (!this.IsHandleCreated)
{
throw new InvalidOperationException(SR.GetString("ErrorNoMarshalingThread"));
}
if (((ActiveXImpl) this.Properties.GetObject(PropActiveXImpl)) != null)
{
IntSecurity.UnmanagedCode.Demand();
}
bool flag = false;
if ((SafeNativeMethods.GetWindowThreadProcessId(new HandleRef(this, this.Handle), out num) == SafeNativeMethods.GetCurrentThreadId()) && synchronous)
{
flag = true;
}
ExecutionContext executionContext = null;
if (!flag)
{
executionContext = ExecutionContext.Capture();
}
ThreadMethodEntry entry = new ThreadMethodEntry(caller, method, args, synchronous, executionContext);
lock (this)
{
if (this.threadCallbackList == null)
{
this.threadCallbackList = new Queue();
}
}
lock (this.threadCallbackList)
{
if (threadCallbackMessage == 0)
{
threadCallbackMessage = SafeNativeMethods.RegisterWindowMessage(Application.WindowMessagesVersion + "_ThreadCallbackMessage");
}
this.threadCallbackList.Enqueue(entry);
}
if (flag)
{
this.InvokeMarshaledCallbacks();
}
else
{ //终于找到你了,PostMessage
UnsafeNativeMethods.PostMessage(new HandleRef(this, this.Handle), threadCallbackMessage, IntPtr.Zero, IntPtr.Zero);
}
if (!synchronous) //如果是异步,那么马上返回吧
{
return entry;
}
if (!entry.IsCompleted) //同步调用没结束,阻塞起来等待吧
{
this.WaitForWaitHandle(entry.AsyncWaitHandle);
}
if (entry.exception != null)
{
throw entry.exception;
}
return entry.retVal;
}
怎么样,我们终于看到PostMessage了吧?通过windows消息机制实现了封送。而需要封送的委托方法作为消息的参数进行了传递。关于其它的代码这里不作进一步解释。
3、InvokeRequired
public bool InvokeRequired
{
get
{
using (new MultithreadSafeCallScope())
{
HandleRef ref2;
int num;
if (this.IsHandleCreated)
{
ref2 = new HandleRef(this, this.Handle);
}
else
{
Control wrapper = this.FindMarshalingControl();
if (!wrapper.IsHandleCreated)
{
return false;
}
ref2 = new HandleRef(wrapper, wrapper.Handle);
}
int windowThreadProcessId = SafeNativeMethods.GetWindowThreadProcessId(ref2, out num);
int currentThreadId = SafeNativeMethods.GetCurrentThreadId();
return (windowThreadProcessId != currentThreadId);
}
}
}
终于看到了,这是在判断windows窗体线程和当前的调用者线程是否是同一个,如果是同一个就没有必要封送了,直接访问这个GUI控件吧。否则,就不要那么直接表白了,就需要Invoke或者BeginInvoke做媒了。
发表评论
-
为什么Winforms控件不支持半透明的背景颜色
2009-03-10 16:51 3490整个Windows Forms的painting框架是基于GD ... -
如何使一个你没有源代码的DLL文件变为强命名的(Strong Name)
2009-05-05 12:27 1521有时候你会需要一个DLL是强命名的,比如你想把这个文件放到GA ... -
如何将datetimepicker默认设置为空?
2009-05-05 20:28 1783在Load中,初始化 this.dateTimePicker1 ... -
适合初学者学习的日期操作范例
2009-05-05 21:16 1047适合初学者学习的日期操作范例 1.显示本月第一天 ... -
DataGridView分页及分页后页面操作(上页、下页、页面跳转)类
2009-05-05 21:53 1692class Pagination { private stat ... -
计算两个日期的时间间隔
2009-08-04 11:10 1178///<summary>///计算两个日期的时间间 ... -
访问IIS元数据库失败
2010-03-02 18:06 1254配置ASP.NET平台时遇到的“访问IIS元数据库失败” ... -
批量删除和插入数据操作 sqlserver
2010-08-03 18:03 1507conn.TranBegin(); conn.TranUpda ... -
asp.net 单点登录
2010-08-13 17:23 1327今天做单点登录玩,现 ... -
datatable 删除行
2010-09-06 11:44 1043先列出正确的写法,如果你只想马上改错就先复制吧, &l ... -
ClientScript.RegisterStartupScript使用说明
2010-09-08 11:22 3212ClientScript.RegisterStartupScr ... -
ClientScript.RegisterStartupScript使用说明二
2010-09-09 15:25 1304ClientScript.RegisterStartupScr ... -
updatePanel下使用uploadFile控件
2010-09-09 15:34 1730uploadFile控件和updatePanel不兼容,是微软 ... -
asp.net forms身份验证控制
2010-09-13 17:10 1507Asp.net的身份验证有有三种,分别是"Windo ... -
Asp.net的身份验证有哪些,区别是什么?
2010-09-13 17:39 1581Asp.net的身份验证有有三种,分别是"Windo ... -
Asp.net的身份验证有哪些,区别是什么?
2010-09-13 17:40 1156Asp.net的身份验证有有三种,分别是"Windo ... -
RoleProvider的重写(转至msdn)
2010-09-13 22:23 1086using System.Configuration.Prov ... -
asp.net gridview 导出时设置文本类型
2010-09-15 18:31 1429gridView 导出时使用以下方法时: /// < ...
相关推荐
这两种方法的使用取决于具体场景的需求:需要立即反馈时使用`Invoke`,而在不影响主线程的情况下执行耗时任务时使用`BeginInvoke`。 总结来说,`Invoke`和`BeginInvoke`方法是C#中用于确保GUI更新安全的重要工具。...
### Invoke与BeginInvoke的区别 #### 一、基本概念 在.NET框架中,`Invoke` 和 `BeginInvoke` 是两个常用的方法,主要用于实现跨线程访问控件或执行操作。这两种方法通常出现在多线程编程场景中,特别是当涉及到UI...
就Invoke和BeginInvoke进行对比介绍,穿插了一些例子在里面
本文将深入探讨invoke和beginInvoke的区别、使用场景以及它们在多线程环境中的重要性。 1. **委托(Delegate)的理解**: - 委托是.NET中一种类型安全的函数指针,它可以引用一个或多个方法。通过委托,我们可以将...
c# 中Invoke和BeginInvoke_区别, 适合初学者,一看就会
C_窗体中Invoke和BeginInvoke方法详解 京华志&精华志出品 希望大家互相学习,互相进步 支持CSDN 支持微软 主要包括C# ASP.NET SQLDBA 源码 毕业设计 开题报告 答辩PPT等
在Windows 7操作系统中安装Microsoft SQL Server 2008时,可能会遇到一个常见的错误,即"Invoke或BeginInvoke不能在控件上被调用,直到窗口句柄被创建"。这个错误通常与应用程序试图在窗口句柄未准备好时执行某些...
在Windows 7操作系统中安装Microsoft SQL Server 2008时,可能会遇到一个特定的问题,即在安装过程中显示“Invoke或BeginInvoke不能在控件上被调用,直到窗口句柄已被创建”的错误。这个错误通常表明安装程序与系统...
在实际应用中,我们经常利用线程来执行耗时操作,然后使用`Invoke`或`BeginInvoke`返回到UI线程进行界面更新。例如,`StartMeth`方法可能会启动一个新的线程`invokeThread`,在该线程中执行耗时任务,任务完成后,...
在.NET框架中,多线程编程...总的来说,理解和正确使用`Invoke`和`BeginInvoke`对于编写高性能、响应迅速的多线程应用程序至关重要。在实际开发中,根据具体需求和场景选择合适的方法,以确保程序的稳定性和用户体验。
例如,在大量更新界面元素时,如果使用Invoke会导致用户界面暂时冻结,而使用BeginInvoke可以让界面在后台更新过程中仍然能够接收用户输入和处理其他事件。 在多线程编程中,我们通常会将计算密集型任务放在工作...
C__BeginInvoke_和Invoke区别 京华志&精华志出品 希望大家互相学习,互相进步 支持CSDN 支持微软 主要包括C# ASP.NET SQLDBA 源码 毕业设计 开题报告 答辩PPT等
根据题目描述的实际应用场景,我们可以进一步理解`Invoke`和`BeginInvoke`的使用场景。 **问题背景**: - 主线程启动了一个新线程。 - 在这个新线程中启动了一个名为`serviceForm`的新窗体。 - 需要在新线程中向`...
C# 跨线程访问UI线程控件 ...第一种:使用控件自带的Invoke或者BeginInvoke方法。 Task.Factory.StartNew(() => { this.button1.Invoke(new Action(() => { this.button1.Text = hello world; })
当在其他线程中尝试修改UI元素时,必须使用`Invoke`或`BeginInvoke`来确保操作在正确的线程中执行。 标题"**C# Invoke调用Demo**"意味着我们将探讨如何在C#应用程序中使用`Invoke`方法处理多线程情况下的UI更新问题...
Control的Invoke和BeginInvoke.txt Invoke和BeginInvoke的真正含义.txt NET异步调用模式.txt TreeView更新线程.txt url.txt VB.NET多线程——创建新线程.txt VB.NET多线程——高级同步技术.txt VB.NET多线程——...
当我们在非UI线程(如后台线程)上修改UI元素时,必须使用`BeginInvoke`或`Invoke`,因为UI元素只能在创建它们的线程(即UI线程)上进行操作。`BeginInvoke`提供异步调用,意味着它不会阻塞当前线程,而是将操作放入...
或者,如果需要返回值,可以使用`Func`: ```csharp private int CalculateSomething() { return 42; } private void NonUiThreadMethod() { Func<int> calculateDelegate = new Func(CalculateSomething)...