zoukankan      html  css  js  c++  java
  • 【转】Invoke 和 BeginInvoke 的区别

    讨论环境:C# .netVS2005

    .net默认所有的可视窗体在主线程内维护,如果某工作线程(主线程之外)想实现对窗体控件的操作,缺省情况下是不允许直接操作的,而要通过 Invoke 方法将其封送到主线程去完成。在Control 类内提供了 Invoke BeginInvoke 两个方法实现该功能,MSDN 帮助中提到,它们的唯一区别是 BeginInvoke 多了“异步执行”四个字。(两方法的具体帮助请自行查看MSDN,这里不多罗嗦了)。

    “异步执行”怎么理解,查了网上的一些解答,通过Reflector查看了两方法的背后源码后,得出如下结论:

    Invoke 引起工作线程的阻塞,BeginInvoke 不引起工作线程的阻塞。

    具体解释一下:我们先假设称主线程(即窗体控件的拥有者)为A线程,工作线程为B线程,如果B线程需要操作窗体控件,那么就要使用 Invoke(或BeginInvoke),将相应的操作通过代理,封送到主线程A(具体的代码实现,不多罗嗦,假设读者已知)。那么.net背后是怎么实现线程间“任务挪移”这一步操作的呢?通过Reflector查看源码后发现,原来 Invoke 将你交给它的委托封装成了一个标准的Windows消息,加进了主线程的消息队列内。回到 Invoke BeginInvoke 的区别上来,如果使用 Invoke,那么 B 线程必须等到A线程响应了传送的消息后 才能得到返回值,而如果使用BeginInvoke,则B线程将消息送到A线程后,马上返回,并不一定等待该消息被A线程响应完成。所以如果A线程处在繁忙状态或休眠状态,使用 Invoke 封送消息就会使得B线程被堵塞,而是用 BeginInvoke 则不然,这就是所谓的“异步执行”了。

    空口无凭,让我们来看看Invoke BeginInvoke 背后的代码:

    public object Invoke(Delegate method, params object[] args)
    {
        using (MultithreadSafeCallScope scope = new MultithreadSafeCallScope())
        {
            return this.FindMarshalingControl().MarshaledInvoke(this, method, args, true);
        }
    }
     
    public IAsyncResult BeginInvoke(Delegate method, params object[] args)
    {
        using (MultithreadSafeCallScope scope = new MultithreadSafeCallScope())
        {
            return (IAsyncResult) this.FindMarshalingControl().MarshaledInvoke(this, method, args, false);
        }
    }

    两方法对应的代码基本一样,除了返回类型外,还有一处细微的差别 MarshaledInvoke 方法的第三个参数:Invoke trueBeginInvoke false。这个参数表示什么意思呢?把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
        {
            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;

    }

    好长的一段代码,看着都眼晕。看看刚才提到的第三个参数的名字 synchronous ,从字面意思看‘是否同步’,OK,再看看最后被加粗的几行代码(还是根据字面意思猜):如果不同步,立刻返回;如果同步,而且没完事,就等一等,最后给出返回值。如果我们的假设成立,那么 Invoke 方法的第三个参数为True,就是要同步;BeginInvoke 方法的第三个参数为 False,就是不同步,即异步。Right,到这里就和 MSDN 帮助上介绍 BeginInvoke “异步执行”就对上了。

    如果按照数学分析归纳法,到此应该说:由以上分析,得证:

    Invoke 引起工作线程的阻塞,BeginInvoke 不引起工作线程的阻塞。

    最后,要补充两点:第一,如果主线程以外的工作线程要操作窗体控件,并非一定要使用Invoke方法,更改下边这个属性也是 OK 的,只是不推荐使用。

    System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = false;

    第二,既然 BeginInvoke 不引起阻塞,那么是否就说明它比 Invoke 好呢。我在自己的程序里,把所有的 Invoke 均变成 BeginInvoke ,运行一段时间后,结果提示 StackOverFlowException 错误,帮助对这个异常的说明是“挂起的方法调用过多而导致执行堆栈溢出时引发的异常”。看来还是要慎用。

    http://www.cnblogs.com/ylgqq/archive/2007/12/28/1018760.html

  • 相关阅读:
    TreeMap<K,V>类
    2020-3-7学习地图
    Thread类
    Redis-Windows中注册Redis后台守护进程
    Redsi-(error) NOAUTH Authentication required.
    2020-3-6学习地图
    Collection<E>接口
    Map<K,V>接口
    Set接口
    List类
  • 原文地址:https://www.cnblogs.com/cappuccino/p/1911226.html
Copyright © 2011-2022 走看看