私はまだWinForm UIのバックグラウンドスレッディングに悩まされています。どうして?ここにいくつかの問題があります:クロススレッドWinFormイベント処理でInvoke/BeginInvokeの不具合を回避するには?
- 私がそれを作成したのと同じスレッドで実行していない限り、明らかに最も重要な問題は、コントロールを変更できません。
- あなたが知っているように、Invoke、BeginInvokeなどは、コントロールが作成されるまで利用できません。
- RequiresInvokeがtrueを返した後でも、BeginInvokeはObjectDisposedをスローすることができ、スローしなくても、コントロールが破棄されてもコードを実行することはありません。
- RequiresInvokeがtrueを返した後であっても、InvokeはInvokeの呼び出しと同時に破棄されたコントロールによって実行待ちの状態で無期限にハングアップする可能性があります。
私はこの問題の洗練された解決策を探していますが、私が探しているものの詳細に入る前に、問題を明確にすると思っていました。これは、一般的な問題を取り上げ、それの後ろにもっと具体的な例を置くことです。この例では、大量のデータをインターネット経由で転送しています。ユーザーインターフェイスは、すでに進行中の転送の進行状況ダイアログを表示できる必要があります。進行状況のダイアログは、常に迅速に更新されるはずです(毎秒5〜20回更新)。ユーザーはいつでも進行状況ダイアログを閉じることができ、必要に応じて再度呼び出すことができます。さらに、議論が見える場合は、すべての進捗イベントを処理しなければならないという主張ができます。ユーザーは進行状況ダイアログでキャンセルをクリックし、イベント引数を変更して操作を取り消すことができます。
は、今私は、制約の次のボックスに収まるソリューションを必要とする:
- ワーカースレッドが実行が完了するまで待つ/コントロール/フォームとブロックのメソッドを呼び出すことができます。
- ダイアログ自体が初期化時などにこの同じメソッドを呼び出すことを許可します(したがってinvokeを使用しません)。
- ハンドリングメソッドまたは呼び出しイベントに負担をかけることなく、ソリューションはイベントサブスクリプション自体のみを変更する必要があります。
- 適切に処理をブロックすると、処理中のダイアログへの呼び出しが呼び出されます。残念ながら、これはIsDisposedをチェックするほど簡単ではありません。
- 任意のイベントタイプで使用できる必要があります(EventHandlerタイプの代理人を想定)。
- 例外をTargetInvocationExceptionに変換してはいけません。
- ソリューションは、上記の.Net 2.0以降
ので、これを解決することができます与えられた制約と協力しなければなりませんか?私は数え切れないほどのブログやディスカッションを通して捜して掘り下げました。私はまだ空手です。
更新:私はこの質問には簡単な答えがないことを認識しています。私は数日間このサイトにいましたが、私は質問に答える多くの経験を持つ人を見ました。私はこれらの個人の一人がこれを十分に解決して週を過ごすことができないようにしています。そうすれば合理的な解決法を構築することができます。
更新#2:はい、問題をもう少し詳しく説明し、何か(何かがあれば)が揺れているかどうかを確認します。私たちが状態を判断できるようにする以下のプロパティは、いくつか懸念を引き起こします。現在のスレッド上またはIsHandleCreatedは、すべての親のためにfalseを返す場合、実行している場合はfalseを返すように文書化
Control.InvokeRequired =。 私は、ObjectDisposedExceptionをスローする可能性があるInvokeRequired実装、またはオブジェクトのハンドルを潜在的に再作成する可能性があることに悩まされています。 InvokeRequiredは呼び出すことができない(Disposeが進行中)ときにtrueを返し、invoke(Create in progress)を使用する必要がある場合でもfalseを返すことができるので、これはすべてのケースで単純に信頼できません。 InvokeRequiredがfalseを返すことがわかる唯一のケースは、IsHandleCreatedが呼び出しの前と後の両方でtrueを返すときです(InvokeRequiredのMSDNドキュメントではIsHandleCreatedのチェックを参照してください)。
Control.IsHandleCreated =ハンドルがコントロールに割り当てられている場合はtrueを返します。それ以外の場合はfalseです。 IsHandleCreatedは安全な呼び出しですが、コントロールがハンドルを再作成している場合には、それが故障する可能性があります。この潜在的な問題は、IsHandleCreatedおよびInvokeRequiredにアクセスしている間にロック(制御)を実行することで解決できるように見えます。
Control.Disposing =コントロールが処理中である場合はtrueを返します。
- Control.IsDisposed =コントロールが破棄されている場合はtrueを返します。 私はDisposedイベントを購読し、BeginInvokeが完了するかどうかを判断するためにIsDisposedプロパティをチェックすることを検討しています。ここでの大きな問題は、Disposing - > Disposedトランジションの間に同期化ロックがないことです。 Disposedイベントを購読した後、Disposing == false & & IsDisposed == falseの場合は、Disposedイベントの発生を確認できない可能性があります。これは、Disposeの実装がDisposing = falseを設定し、Disposed = trueを設定するためです。これにより、処分されたコントロールに対してDisposingとIsDisposedの両方をfalseとして読み取ることができます(ただし小さい)。
...頭が痛い:(うまくいけば、上記の情報は、誰もがこれらのトラブルを持つために問題にもう少し光を当てるだろう。私はこれであなたの予備の思考サイクルに感謝。に迫っ
トラブル...次はControl.DestroyHandle()メソッドの後半である:
if (!this.RecreatingHandle && (this.threadCallbackList != null))
{
lock (this.threadCallbackList)
{
Exception exception = new ObjectDisposedException(base.GetType().Name);
while (this.threadCallbackList.Count > 0)
{
ThreadMethodEntry entry = (ThreadMethodEntry) this.threadCallbackList.Dequeue();
entry.exception = exception;
entry.Complete();
}
}
}
if ((0x40 & ((int) ((long) UnsafeNativeMethods.GetWindowLong(new HandleRef(this.window, this.InternalHandle), -20)))) != 0)
{
UnsafeNativeMethods.DefMDIChildProc(this.InternalHandle, 0x10, IntPtr.Zero, IntPtr.Zero);
}
else
{
this.window.DestroyHandle();
}
あなたは説明ObjectDisposedExceptionが待機中のすべてのクロススレッド呼び出しに派遣されて気づく間もなく、これはこれへの呼び出しで次のよう。 .window.DestroyHandle()は、ウィンドウとseを順番に破棄します。これはIntPtr.Zeroへのハンドル参照で、BeginInvokeメソッド(またはBeginInvokeとInvokeの両方を処理するMarshaledInvoke)へのさらなる呼び出しを防ぎます。ここでの問題は、threadCallbackListのロックが解放された後で、コントロールのスレッドがウィンドウハンドルを0にする前に新しいエントリを挿入できることです。これは、まれにしかリリースを停止するのに十分な頻度で頻繁に見られているようです。
更新#4:
残念ですが、このままドラッグしてください。しかし、私はそれがここで文書化する価値があると思った。私は上記の問題のほとんどを解決することができましたし、私はその解決策に絞っています。私は懸念していたもう一つの問題にぶつかったが、今までは、「野生のもの」は見ていない。
この問題は、コントロールを作成した天才と関係があります。ハンドルのプロパティ:
public IntPtr get_Handle()
{
if ((checkForIllegalCrossThreadCalls && !inCrossThreadSafeCall) && this.InvokeRequired)
{
throw new InvalidOperationException(SR.GetString("IllegalCrossThreadCall", new object[] { this.Name }));
}
if (!this.IsHandleCreated)
{
this.CreateHandle();
}
return this.HandleInternal;
}
これだけでは(get {}の変更に関する私の意見に関係なく)それほど悪くありません。ただし、InvokeRequiredプロパティまたはInvoke/BeginInvokeメソッドと組み合わされた場合、それは悪いことです。ここでは基本的な流れは、Invokeです:
if(!this.IsHandleCreated)
throw;
... do more stuff
PostMessage(this.Handle, ...);
ここでの問題は、別のスレッドから、私は成功したようにGETを引き起こし、ハンドルがコントロールのスレッドによって破壊された後、最初のif文を通過することができるということです私のスレッドでウィンドウハンドルを再作成するためのプロパティを処理します。これにより、元のコントロールのスレッドで例外が発生する可能性があります。このことを守る方法がないので、私は本当に私を困惑させています。 InternalHandleプロパティのみを使用し、IntPtr.Zeroの結果をテストした場合、これは問題にはなりません。
質問をしているうちにちょっと丁寧になります。 –
LOL、Ok ...私はそれをきれいにするでしょう。 –