zoukankan      html  css  js  c++  java
  • 研究BackgroundWorker后发现:AsyncOperation和SynchronizationContext的差异真的很大!

    今天研究BackgroundWorker代码时发现,两处代码的写法有些不一致,于是好奇的测试了一番,以为能测出BackgroundWorker的一个bug。结果大家都知道microsoft胜了。

    下面来看看过程,BackgroundWorker类里的ReportProgress方法

    public void ReportProgress(int percentProgress, object userState)
    {
    if (!this.WorkerReportsProgress) throw new InvalidOperationException("BackgroundWorker_WorkerDoesntReportProgress");
    ProgressChangedEventArgs arg = new ProgressChangedEventArgs(percentProgress, userState);
    if (this.asyncOperation != null)//这里对asyncOperation加了判断
    this.asyncOperation.Post(this.progressReporter, arg);
    else
    this.progressReporter(arg);
    }

    BackgroundWorker的WorkerThreadStart方法

            private void WorkerThreadStart(object argument)
            {
                object result = null;
                Exception error = null;
                bool cancelled = false;
                try
                {
                    DoWorkEventArgs e = new DoWorkEventArgs(argument);
                    this.OnDoWork(e);
                    if (e.Cancel)
                        cancelled = true;
                    else
                        result = e.Result;
                }
                catch (Exception exception2)
                {
                    error = exception2;
                }
                RunWorkerCompletedEventArgs arg = new RunWorkerCompletedEventArgs(result, error, cancelled);
     
                //这里没有加判断
                this.asyncOperation.PostOperationCompleted(this.operationCompleted, arg);
            }

    上面两个方法中,一个判断asyncOperation为null,一个不判断。asyncOperation的创建是在RunWorkerAsync方法中完成的。一直觉得微软的代码都是写得很严谨的,他这样写一定有道理。于是去深入挖掘asyncOperation是如何创建的:

            public void RunWorkerAsync(object argument)
            {
                if (this.isRunning) throw new InvalidOperationException("BackgroundWorker_WorkerAlreadyRunning");
                this.isRunning = true;
                this.cancellationPending = false;
                //创建一个asyncOperation
                this.asyncOperation = AsyncOperationManager.CreateOperation(null);
    
    
                this.threadStart.BeginInvoke(argument, null, null);
            }

    一般来说,这个方法应该在UI线程中调用,这样asyncOperation中的SynchronizationContext就会是一个WindowsFormsSynchronizationContext,对于更新和完成事件中对于UI控件的更新就不会出问题。但是,如果调用RunWorkerAsync方法的不是UI线程呢?带着这个疑问,我故意启动一个新的线程来初始化一个BackgroundWorker,结果就出现了线程间的非法访问。但是没加判断的那句代码的this.asyncOperation应该为null才对呀,所有虽然是异常了,但不是我预期的NullException异常。于是只好再往下挖掘asyncOperation的创建。

    BackgroundWorker内部使用的是AsyncOperation。大家都知道,AsyncOperation是对SynchronizationContext的一个包装。

    1,当我们在一个非UI线程上调用SynchronizationContext.Current会返回一个null。

    2,当我们在一个非UI线程上调用AsyncOperationManager.CreateOperation创建一个AsyncOperation,是不是也应该返回null呢?

    事与愿违,它永远都不会为null,这也就罢了,包装在里面的SynchronizationContext该为null了吧?

    再次失望,它还是不为null。

    来看看真相吧:

        public static class AsyncOperationManager
        {
            public static AsyncOperation CreateOperation(object userSuppliedState)
            {
                return AsyncOperation.CreateOperation(userSuppliedState, SynchronizationContext);
            }
    
            [EditorBrowsable(EditorBrowsableState.Advanced)]
            public static System.Threading.SynchronizationContext SynchronizationContext
            {
                get
                {
                    //下面这句就是根源,当前线程没有同步上下文,会创建一个新的上下文,晕!
                    if (System.Threading.SynchronizationContext.Current == null) System.Threading.SynchronizationContext.SetSynchronizationContext(new System.Threading.SynchronizationContext());
                    return System.Threading.SynchronizationContext.Current;
                }
                [PermissionSet(SecurityAction.LinkDemand, Name="FullTrust")]
                set
                {
                    System.Threading.SynchronizationContext.SetSynchronizationContext(value);
                }
            }
        }

    看看上面的代码就明白了,如果当前线程的同步上下文为null,它就恒定的为工作者线程设置了一个SynchronizationContext,而SynchronizationContext的Post方法只是将代理事件简单的放入线程池,来达到异步的目的。所以真正执行我们的事件回调的线程不是UI线程,当然要报线程间非法访问UI控件的错。

    public virtual void Post(SendOrPostCallback d, object state)
    {
        ThreadPool.QueueUserWorkItem(new WaitCallback(d.Invoke), state);
    } 

    总结一下上面所要表达的:

    1,上面的BackgroundWorker的代码明显写得不够严谨,两个函数都不用加null判断,误导读者(当然也没人叫我们去反编译微软的代码-_-)。

    2,如果用SynchronizationContext就要判断是不是为null,用AsyncOperation就不用判断。

    3,对RunWorkerAsync的调用只能放在UI线程中,否则不能将回调封送到UI线程中。

    4,弄清楚他们的区别后,你用哪一个都不是问题。

  • 相关阅读:
    IDEA忽略某些文件
    Mac操作:Mac系统移动鼠标显示桌面(移动鼠标到角落)
    Mac流程图的软件
    Mac 电脑无法登陆 账号了
    学习如何管理-录视频
    项目倒入maven 遇到的问题只有 main 了
    下载代码的时候 SSH与http的区别
    Mac修改hosts方法
    MAC版本的UltraEdit破解方法
    intelliJ 社区版-找不到 plugins选项
  • 原文地址:https://www.cnblogs.com/xiashengwang/p/2650981.html
Copyright © 2011-2022 走看看