WinFormsを使ったスレッディング

私のアプリケーションでは、メインのUIスレッドを追跡するために作成された隠された dummyForm を起動しました。したがって、新しいフォームを作成しようとしている場合、新しいフォームを作成するときにメインUIスレッド上にあることを確認するために、ダミーフォームで InvokeRequired が使用されます。

私の frmStart フォームをインスタンス化した直後に、 frmStart.InvokeRequired をチェックし、falseに設定するので、ここで呼び出す必要はありません( dummyForm.InvokeRequired )。

次に、 frmStart を親/所有者として使用する frmMyDialog

using(Create frmMyDialog on main UI thread)
{
    frmMyDialog.Show(frmStart);
}

これは、クロススレッドの例外をスローし、ここでは奇妙なことです:

frmMyDialog.InvokeRequired = false
dummyForm.InvokeRequired = false
frmStart.InvokeRequired = true

これは、 frmStart を作成するときに dummyForm.InvokeRequired がfalseであることを確認しているときでもあります。

frmMyDialog.InvokeRequired は常に dummyForm.InvokeRequired と同じ値にする必要があります。ここで何が起きてるの?

最初のインスタンスが作成された後、 frmStart および dummyForm が再作成されていないことを確認しました。

Edit1:

アプリケーションの起動方法は次のとおりです。

public static void Main(string[] args)
{
     _instance = new MyClientMain(parameters);
     Application.Run(_instance);
}

MyClientMainクラスのコンストラクタは、MainControlという静的クラスでセットアップを実行します。 MainControlerはsetupメソッドで次のようなダミーフォームをインスタンス化します:

if (_dummyForm == null)
   _dummyForm = new Form();

これが行われた後、ログインフォームはログインを処理し、このフォームはマルチスレッド化されます。ログインが終了すると、MainControllerが再び呼び出され、frmStartを保持するメインのMDIウインドウをインスタンス化してオープンします。同じスレッド上にいることを確認するには、次のようにします。

public static StartApplication()
{
if (_dummyForm.InvokeRequired)
                    _dummyForm.Invoke(new MethodInvoker(delegate { OpenMainOrbitWindow(); }));
     //Instanciate mainform and frmStart then open mainForm with frmStart as a MDI child

}

ここにはマルチスレッドはありません。

その後、サービスがオフラインになると、イベントがトリガされ、frmMyDialogをポップアップする必要があります.ShowDialog()を使用すると、ダイアログがフォームの背後に配置されるため、親/所有者が最も多く見つかって設定されます。

public static Form GetActiveForm()
        {
            Form activeForm = Form.ActiveForm;

            if (activeForm != null)
                return activeForm;

            if (MainOrbitForm.TopMost)
                return MainOrbitForm;
            else
            {
                FormCollection openForms = Application.OpenForms;
                for (int i = 0; i < openForms.Count && activeForm == null; ++i)
                {
                    Form openForm = openForms[i];
                    if (openForm.IsMdiContainer)
                        return openForm.ActiveMdiChild;
                }
            }

            if (_patientForm != null)
            {
                if (_patientForm.TopMost)
                    return _patientForm;
            }

            return null;
        }

        public static string ShowOrbitDialogReName()
        {
            frmMyDialog myDialog;
            Form testForm;

            //Makes sures that the frmOrbitDialog is created with the same thread as the dummyForm
            //InvokeRequired is used for this
            using (myDialog = MainController.CreateForm())
            {
               //Settings...
               testForm = GetActiveForm();
               myDialog.ShowDialog(GetActiveForm(testForm));


            }
        }

問題はそれです

myDialog.InvokeRequired = false
testForm.InvokeRequired = true;
MainController.DummyForm.InvokeRequired = false;

Edit2: Startup and creates the dummyform :

dummyForm.InvokeRequired = false
Thread.CurrentThread.ManagedThreadId = 9 

成功したログイン後、メインフォームを作成します

_mainForm.InvokeRequired = false
MainControl.DummyForm.InvokeRequired = false
Thread.CurrentThread.ManagedThreadId = 9

これまでのところすべてがうまく見えます。次にコールバックが受信され(WCF)、イベントがfrmMyDialogを同じスレッド上に作成します(InvokeはdummyFormで使用されます)。次にShowDialogが使用されます。

frmMyCustomDialog.ShowDialog(_mainForm)

これはCrossThreadExceptionをスローし、これはこの時点でどのように見えるかです:

_mainForm.InvokeRequired = true
frmMyCustomDialog.InvokeRequired = false
MainControl.DummyForm.InvokeRequired = false
Thread.CurrentThread.ManagedThreadId = 12

MainControl.DummyFormが真でないのはなぜですか? ManageThreadIdは9ではなく12ですか?

4
状態に関係なく呼び出しを強制するだけの場合はどうなりますか?
追加された 著者 James,
@JodrellはEdit1を参照してください
追加された 著者 Banshee,
@Janの嘆願はEdit1を参照してください
追加された 著者 Banshee,
@ジェームス私はこれをどこでお勧めしますか?
追加された 著者 Banshee,
@James Edit2の嘆願を見てください。
追加された 著者 Banshee,
Application.Runを複数回呼びますか?
追加された 著者 Jodrell,
あなたが得る例外に関連するフォームをどのように作成するかについて、より多くの本当のコードを示してください。 InvokeRequiredはメインUIスレッドにバインドされていませんが、コントロール/フォームが作成されたスレッドにバインドされています。
追加された 著者 Jan,
私の頭の中に多すぎるフォームエラー - 私は:)
追加された 著者 Jan,

3 答え

System.Threading.SynchronizationContext.Current を使用する必要があります。このような目的のために直接作成されました。 アプリケーションの最初のフォームが作成されたら、どこにでもアクセスできます。下の例で判断すると、アプリケーションの開始直後にフォームを作成するので、これは問題ではありません。

public static void Main(string[] args)
{
     _instance = new MyClientMain(parameters);
     Application.Run(_instance);
}

そして、どこでも、UIスレッドでコードを実行する必要があります。

System.Threading.SynchronizationContext.Current.Send()//To execute your code synchronously
System.Threading.SynchronizationContext.Current.Post()//To execute your code synchronously

SynchronizationContextはスマートであり、UIスレッドから既に呼び出されているだけで、デリゲートを直接実行します。 また、SynchronizationContextを初めて使用する前に、いくつかのWinFormsフォームまたはコントロールを作成する必要があることを忘れないでください。これを行うと、コンテキストは適切な実装に初期化されます。

3つの実装があります:デフォルト、何もしません - 常に同期してコードを実行し、WinFormsコントロールまたはWPFコントロールを作成するまでCurrentにとどまります。 CurrentにWinformsのコンテキスト、またはWPFディスパッチャーのコンテキストが設定されます。

2
追加された
ありがとう、私はフォームを作成して開いてもSystem.Threading.SynchronizationContext.Currentはnullですか?
追加された 著者 Banshee,
まあ、明らかにSynchronizationContextはスレッドごとにシングルトンで、frmMyDialogが表示されようとしているときに別のスレッドにいるようです。
追加された 著者 Banshee,
いいえ、問題は、すべてのスレッドがSynchronizationContextを持っているわけではなく、スレッドごとにシエルルトンです。 Imをダイアログを開こうとすると、別のスレッド(dummyFormを作成したスレッドではなく、WCFコールバックから作成されたスレッド)であり、このスレッドにはSynchronizationContextがありません。私は静的MainControllerのメインのUIスレッドのSynchronizationContextを格納してダミーフォームの代わりに使用することができましたが、これは良い解決策ではありませんか?
追加された 著者 Banshee,
SynchronizationContextについての素晴らしい記事はこちら> codeproject.com/KB/threads/SynchronizationContext.aspx
追加された 著者 Banshee,
私はこれをもう少しテストしましたが、それについての情報を見つけるのは簡単ではありませんが、WindowsFormsSynchronizationContext.Currentを使用する必要があるようです。しかしsomの質問が残っています。
追加された 著者 Banshee,
フォームを表示させる必要があります。
追加された 著者 Vladimir Perevalov,
誤解を招くような例が少しありました(SynchronizationContextを使用しています)。私は古いソースを見てきました。 UIスレッドで取得したSynchronizationContextをどこかに保存する必要があります。そして、その保存されたインスタンスを使用します。その後、すべて正常に動作します。 SynchronizationContextは、UIフレームワークの1つがスレッドで作業を開始した後で初期化されます。
追加された 著者 Vladimir Perevalov,

これは私の頭の上からちょうど離れたところですが、Vladimir Perevalovが別の議論で述べたように、フォームを見えるようにする必要があります。

あなたのfrmDummyが一度も表示されない場合は、ウィンドウハンドルが作成され割り当てられることはありませんので、常にFalseを "InvokeRequired"に返信します。これは、frmDummyを介して同期することを意味するすべてのコードが実際には最初のUIスレッドに送信されることはなく、常に現在のスレッドで実行されることを意味します。 (これは今作成したコントロールのための独自のUIスレッドになります)。

重要なことは、InvokeRequiredは、そのコントロールのウィンドウハンドルが別のスレッドによって所有されているかどうかを判断しようとすることです。それはコンストラクタとは何の関係もありません。

frmDummyを表示したくない場合は、CreateControlをインスタンス化した直後に、そのハンドルが割り当てられていることを確認することができます。

1
追加された

私は完全にあなたの質問を理解していない、あなたの例では本当にマルチスレッドについては何も表示されないため - しかし、親が別のスレッドの別のフォームであるフォームを作成する場合は、このコードを使用することができます:

public void CreateShowDialogForm()
{
  if (this.InvokeRequired)
  {
    this.Invoke(new Action(CreateShowDialogForm));
  }
  else
  {
    Form frmMyDialog = new Form();
    frmMyDialog.Show(this);
  }
}

private void Form4_Load(object sender, EventArgs e)
{
  Task t = new Task(() => CreateShowDialogForm());
  t.Start();
  t.ContinueWith(task => true);
}
0
追加された
うん、これが正しいかどうかわからない、喜んでEdit1を見て
追加された 著者 Banshee,