2016-11-18 11 views
2

進行状況メーターコントロールを表示するWPFダイアログと、進行状況の更新を示すバックグラウンドタスク(System.Threading.Tasks.Task)があります。プログレスメーター。 2人の間のメディエーターはSystem.Progress<T>オブジェクトです。WinFormsダイアログが表示された後、メインスレッド上でイベントが発生しないSystem.Progress

このすべてが「通常」の状況下で完璧に動作します:

  • バックグラウンドタスクは、メインスレッドではない、いくつかのスレッドX上System.IProgress.Report()を呼び出します。
  • System.Progressオブジェクトは、System.Progressオブジェクトは、メインスレッド上ProgressChangedイベントが発生したスレッド
  • を切り替え、内部の魔法を行います。
  • 進捗メータ制御が、私は任意ののWinFormsダイアログを開いた場合、その後、その後、それを閉じて私のバックグラウンドタスクを開始し、

今(コントロールを所有している)は、メインスレッド上で更新され、System.Progressが突然発火しますProgressChangedイベントはメインスレッドではなく、メインスレッドではないスレッドYにあります。イベントハンドラがコントロールを所有しているスレッドとは別のスレッドでWPF進行状況メータコントロールを更新しようとするため、これはもちろんInvalidOperationExceptionになります。

私はそれを言ってSystem.Progressためのドキュメントを気づい:

を、インスタンスの作成時に[...] ProgressChangedイベントに登録されたイベントハンドラがキャプチャSynchronizationContextインスタンスによって呼び出されます。構築時に現在のSynchronizationContextがない場合、ThreadPoolでコールバックが呼び出されます。

これはSystem.Progressが悪い場合には、そのイベントを発生時にコールスタックの下の部分がどのように見えるかであるので、これは、私が観察できるものと一致しているようだ:

[...] 
bei System.Progress`1.InvokeHandlers(Object state) 
bei System.Threading.QueueUserWorkItemCallback.WaitCallback_Context(Object state) 
bei System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) 
bei System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) 
bei System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem() 
bei System.Threading.ThreadPoolWorkQueue.Dispatch() 
bei System.Threading._ThreadPoolWaitCallback.PerformWaitCallback() 

私はの値をチェックしますSystem.Progressオブジェクトが作成された時点ではSynchronizationContext.Currentプロパティですが、nullになることはありません。プロパティによって返さSynchronizationContextオブジェクトには、次の種類があります。

  • 良い場合(つまりリサイズダイアログを開く前に):オブジェクト:オブジェクトはSystem.Windows.Forms.WindowsFormsSynchronizationContext
  • 悪い場合(つまりリサイズダイアログを開いた後)でありますSystem.Threading.SynchronizationContext

は、残念ながら私はWinFormsのではない多くの経験、そして全くSynchronizationContextとどれを持っているので、私はここで何が起こっているかの損失でかなり午前です。

なぜWinFormsダイアログを開くとSynchronizationContext.Currentの値が変わるのですか?これはなぜSystem.Progressの動作に影響しますか? System.Progressの自分自身の書き換えを書いていないのに、問題を「修正」する方法はありますか?

EDIT:実行ファイルがコアでMFCアプリケーションであり、.exeプロジェクトが/ CLRでコンパイルされ、見ているC#コードがC++/CLI経由で呼び出されることがあります。 C#コードは.NET Framework 4.5.1用にコンパイルされています(実行中)。複雑な設定は、アプリケーションが現代的な態度を持つ伝統的な獣であるためです:-)これまでのところ、これは私たちのために非常にうまくいっています。

+0

メインUI以外のスレッドでダイアログフォームを表示していますか? –

+0

@IvanStoevいいえ、私はメインのUIスレッドでWinFormsダイアログを表示しています。また、メインのUIスレッドでWPFの進行状況ダイアログを表示しています。 – herzbube

+0

あなたが正しいように見えます。 'WindowsFormsSynchronizationContext'は、内部メッセージループ回数が0になると自動的にアンインストールされます。通常、WFアプリケーションでは発生しません。メインループ(' Application.Run')は常に1つあります。たとえあなたが何とかそれを騙しても、実際に正しく動作するためにはメッセージループをサポートする必要があります。 –

答えて

2

興味深い発見。既定では、WindowsFormSynhronizationContextは、Controlクラス(Formを含む)コンストラクタの内側と最初のメッセージループ内に自動的にインストールされ、最後のメッセージループの後にアンインストールされます。 WinFormsアプリケーションは通常、Application.Runコールの内部に存在するため、このアンインストール動作は監視されません。

あなたの場合はありません。出力された

using System; 
using System.Diagnostics; 
using System.Threading; 
using System.Windows.Forms; 

namespace WindowsFormsApplication1 
{ 
    static class Program 
    { 
     [STAThread] 
     static void Main() 
     { 
      var form = new Form(); 
      Trace.WriteLine(SynchronizationContext.Current?.GetType().ToString() ?? "null"); 
      form.ShowDialog(); 
      Trace.WriteLine(SynchronizationContext.Current?.GetType().ToString() ?? "null"); 
     } 
    } 
} 

::回避策として

System.Windows.Forms.WindowsFormsSynchronizationContext 
System.Threading.SynchronizationContext 

、私はあなたが最初に手動でメインUIスレッドSynchronizationContextを設定することをお勧め問題は、簡単に次のような単純なWFアプリケーションで再生可能その後、アプリケーションとそのアンインストール動作を防ぐことができます(ただし、アプリケーションの他の部分は、メインスレッドSynchronizationContextを置き換えた場合の問題を引き起こす可能性があります)AutoInstallオフ、ターン:

SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext()); 
WindowsFormsSynchronizationContext.AutoInstall = false; 
+0

私は今週これを調べ、いくつかのフィードバックを提供します。 – herzbube

+0

はい、これは私たちのために働きます。 – herzbube

関連する問題