BackgroundWorkerを研究したところ、AsyncOperationとSynchronizationContextの違いは本当に大きいことがわかりました.

9483 ワード

今日BackgroundWorkerコードを研究したところ、2つのコードの書き方が一致していないことがわかり、BackgroundWorkerのバグを測定できると思って好奇心を持ってテストしました.結局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);
        }

上記の2つの方法のうち,1つはasyncOperationがnullであると判断し,1つは判断しない.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のはずですね.すべて異常ですが、私が予想したNull Exception異常ではありません.そこでasyncOperationの作成をさらに掘り下げるしかなかった.
BackgroundWorker内部ではAsyncOperationを使用しています.AsyncOperationはSynchronizationContextのパッケージであることはよく知られています.
1 UI以外のスレッドでSynchronizationContextを呼び出すとCurrentはnullを返します.
2 UI以外のスレッドでAsyncOperationManagementを呼び出すと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のコードは明らかに厳密ではありません.2つの関数はnull判断を加える必要はありません.読者を誤導します(もちろん、マイクロソフトのコードを逆コンパイルするように言われていません--).
2,SynchronizationContextを使えばnullかどうかを判断し,AsyncOperationを使えば判断しない.
3,RunWorkerAsyncの呼び出しはUIスレッドにしか入れられません.そうしないと、コールバックをUIスレッドに封入することはできません.
4、彼らの違いを明らかにしたら、どちらを使っても問題ではありません.