分享

线程

 kittywei 2012-03-29

Invoke或者BeginInvoke的使用中无一例外地使用了委托Delegate,至于委托的本质请参考我的另一随笔:.net事件的看法
 

一、Control提供了InvokeBeginInvoke机制?

问题的最主要的原因已dotnet程序众所周知的,我在此点笔墨再次记录到自己的日志,以便日后提醒一下自己。

1windows程序消息机制

Windows GUI程序是基于消息机制的,有个主线维护着一个消息个消息泵让windows程序生生不息。

                                                  Windows GUI程序的消息循

 

 

Windows程序有个消息列,窗体上的所有消息是列里面消息的最主要来源。里的while使用了GetMessage()个方法,是个阻塞方法,也就是方法就会被阻塞,从而while停止运避免了一个程序把cpu无故地耗尽,其它程序以得到响。当然在某些需要cpu最大限度运的程序里面就可以使用另外的方法,例如某些3d或者及时战略游中,一般会使用PeekMessage()个方法,它不会被windows阻塞,从而保整个游的流和比高的速。

个主线维护着整个窗体以及上面的子控件。当它得到一个消息,就会DispatchMessage方法派遣消息,会引起窗体上的窗口程的用。窗口程里面当然是程序提供的窗体数据更新代和其它代

2dotnet里面的消息循

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接口,提供了InvokeBeginInvoke方法来提供其它线程更新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方法,通一个委托把用封送到控件所属的线程上行。

二、消息机制---线通信机制

1window消息

Windows消息机制是windows平台上的线程或者通信机制之一。Windows消息就是定的一个数据构,最重要的是消息的型,它就是一个整数;然后就是消息的参数。消息的参数可以表示很多西。

Windows提供了一些api用来向一个线程的消息送消息。因此,一个线程可以向另一个线程的消息送消息从而告诉对方做什这样就完成了线的通信。有些api送消息需要一个窗口句柄,这种函数可以把消息送到指定窗口的主线程消息列;而有些可以直接通过线程句柄,把消息送到该线程消息列中。

                                                   

 

用消息机制通信

 

SendMessagewindows api,用来把一个消息送到一个窗口的消息列。个方法是个阻塞方法,也就是操作系会确保消息的确送到目的消息列,并且消息被理完以后,函数才返回。返回之前,用者将会被暂时阻塞。

PostMessage也是一个用来送消息到窗口消息列的api函数,但个方法是非阻塞的。也就是它会上返回,而不管消息是否真的送到目的地,也就是用者不会被阻塞。

2Invoke and BeginInvoke

 

                                                        Invoke or BeginInvoke

 

Invoke或者BeginInvoke方法都需要一个委托象作参数。委托似于回函数的地址,因此用者通过这两个方法就可以把需要用的函数地址封送界面线程。些方法里面如果包含了更改控件状的代,那由于最终执个方法的是界面线程,从而避免了争条件,避免了不可料的问题。如果其它线程直接操作界面线程所属的控件,那将会争条件,造成不可料的果。

使用Invoke完成一个委托方法的封送,就似于使用SendMessage方法来界面线消息,是一个同方法。也就是Invoke封送的方法被行完前,Invoke方法不会返回,从而用者线程将被阻塞。

使用BeginInvoke方法封送一个委托方法,似于使用PostMessage行通信,是一个异方法。也就是方法封送完上返回,不会等待委托方法的束,用者线程将不会被阻塞。但是用者也可以使用EndInvoke方法或者其它WaitHandle机制等待异操作的完成。

但是在内部实现上,InvokeBeginInvoke都是用了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察看一些相

1Control.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);

    }

}

里的FindMarshalingControl方法通一个循向上回溯,从当前控件始回溯父控件,直到找到最顶级的父控件,用它作封送象。例如,我们调用窗体上一个度条的Invoke方法封送委托,但是实际上会回溯到主窗体,通过这个控件象来封送委托。因主窗体是主线程消息列相的,主窗体的消息才能送到界面主线程消息列。

可以看到InvokeBeginInvoke方法使用了同实现,只是MarshaledInvoke方法的最后一个参数不一

2MarshaledInvoke

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消息机制实现了封送。而需要封送的委托方法作消息的参数行了传递于其它的代码这里不作

3InvokeRequired

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做媒了

 

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多