多くの場所で、それが 標準的な知恵 であることを見てきました1 UIコンポーネントを更新するときに、UIスレッド上にいることを確認するのは呼び出し側の責任です(具体的には、Java Swingでは、 Event Dispatch Thread )。
これはなぜですか?イベントディスパッチスレッドは、MVC/MVP/MVVMのviewの問題です。ビュー以外の場所でそれを処理するには、ビューの実装とそのビューの実装のスレッドモデルの間に密結合を作成します。
具体的には、Swingを使用するMVC設計のアプリケーションがあるとします。呼び出し元がイベントディスパッチスレッドのコンポーネントの更新を担当している場合、Swing View実装をJavaFX実装に交換しようとすると、JavaFXを使用するようにすべてのPresenter/Controllerコードを変更する必要があります代わりにアプリケーションスレッド。
だから、私は2つの質問があると思います:
いくつかのMCVE Javaコードを追加して、「呼び出し側の責任」が何を意味するかを説明します(ここでは、私が行っていない他のいくつかの優れた実践がありますが、意図的に最小限)):
責任を負う発信者:
public class Presenter {
private final View;
void updateViewWithNewData(final Data data) {
EventQueue.invokeLater(new Runnable() {
public void run() {
view.setData(data);
}
});
}
}
public class View {
void setData(Data data) {
component.setText(data.getMessage());
}
}
責任があると見る:
public class Presenter {
private final View;
void updateViewWithNewData(final Data data) {
view.setData(data);
}
}
public class View {
void setData(Data data) {
EventQueue.invokeLater(new Runnable() {
public void run() {
component.setText(data.getMessage());
}
});
}
}
1:その投稿の作成者は、Stack OverflowのSwingで最も高いタグスコアを持っています。彼は至る所でこれを言っており、私はそれが他の場所でも呼び出し側の責任であるのを見ました。
彼の 失敗した夢のエッセイ の終わりに向かって、Graham Hamilton(メジャーJavaアーキテクト)は、開発者が「イベントキューモデルとの同等性を維持する必要がある場合、さまざまな非自明のルールに従う必要があり、可視で明示的なイベントキューモデルを使用することで、「モデルをより確実にフォローできるようになり、確実に機能するGUIプログラムを構築できるようになります。」
つまり、イベントキューモデルの上にマルチスレッドのファサードを配置しようとすると、抽象化が、デバッグが非常に困難な明白でない方法でリークすることがあります。紙の上でうまくいくように見えますが、生産ではバラバラになってしまいます。
単一のコンポーネントの周りに小さなラッパーを追加しても、ワーカースレッドからプログレスバーを更新する場合など、問題が発生することはおそらくありません。複数のロックを必要とするより複雑なことを実行しようとすると、マルチスレッドレイヤーとイベントキューレイヤーがどのように相互作用するのかを考えるのが非常に難しくなります。
これらの種類の問題は、すべてのGUIツールキットに共通することに注意してください。プレゼンター/コントローラーのイベントディスパッチモデルが、1つの特定のGUIツールキットの同時実行モデルのみに緊密に結合しているのではなく、それらすべてに結合していると想定しています 。イベントキューイングインターフェイスを抽象化するのはそれほど難しいことではありません。
GUIのlibスレッドを安全にすることは、大きな頭痛とボトルネックになるからです。
GUIの制御フローは、多くの場合、イベントキューからルートウィンドウ、GUIウィジェット、アプリケーションコードからルートウィンドウまで伝播されたウィジェットの2方向に進みます。
ルートウィンドウをロックしない(多くの競合を引き起こす)ロック戦略を考案するのはdifficultです。別のスレッドがトップダウンをロックしているときにボトムアップをロックすることは、インスタントデッドロックを達成するための優れた方法です。
また、現在のスレッドがguiスレッドであるかどうかを毎回チェックするとコストがかかり、特に読み取り更新書き込みシーケンスを実行しているときに、guiで実際に何が起こっているかについて混乱を招く可能性があります。競合を回避するには、データをロックする必要があります。
スレッド化(共有メモリモデル内)は、抽象化の取り組みを無視する傾向があるプロパティです。単純な例はSet
タイプです。Contains(..)
とAdd(...)
とUpdate(...)
は、シングルスレッドシナリオで完全に有効なAPIですが、マルチスレッドシナリオにはAddOrUpdate
が必要です。
同じことがUIにも当てはまります。リストの一番上にあるアイテムの数とともにアイテムのリストを表示する場合は、変更するたびに両方を更新する必要があります。
それらのどれも本当に魅力的に見えません。プレゼンターがスレッド処理を担当するようにすることは、それが優れているためではなく、機能し、代替手段が悪いために推奨されます。
私はこの問題(カールビーレフェルトが言及)について議論する本当に良いブログを少し前に読みました、それは基本的に、UIキットをスレッドセーフにすることは非常に危険であるということです。フレームワークに実装された競合状態。
パフォーマンスに関する考慮事項もあります。今のところそれほどではありませんが、Swingが最初にリリースされたとき、パフォーマンスは悪いと批判されていましたが、それは実際にはSwingのせいではなく、使用方法に関する知識の欠如でした。
SWTは、違反した場合に例外をスローすることにより、スレッドの安全性の概念を適用しますが、少なくともあなたはそれを認識しています。
たとえば、ペイントプロセスを検討する場合、要素がペイントされる順序は非常に重要です。 1つのコンポーネントのペイントが画面の他の部分に副作用を与えたくない場合。ラベルのテキストプロパティを更新できたが、2つの異なるスレッドによってペイントされた場合、出力が破損する可能性があると想像してください。したがって、すべてのペイントは単一のスレッド内で行われ、通常は要件/要求の順序に基づいています(ただし、実際の物理的なペイントサイクルの数を減らすために圧縮される場合があります)。
SwingからJavaFXへの変更について言及しましたが、ほぼすべてのUIフレームワーク(シッククライアントだけでなくWebも同様)でこの問題が発生しますが、Swingは問題を強調するもののようです。
中間層(コントローラーのコントローラー?)を設計して、UIへの呼び出しが正しく同期されるようにすることができます。 UI APIの観点から、APIの非UI部分をどのように設計するかを正確に知ることは不可能であり、ほとんどの開発者は、UI APIに実装されたスレッド保護が制限的である、またはニーズを満たさなかったと不平を言うでしょう。ニーズに基づいて、この問題の解決方法を決定できるようにする方が良い
考慮する必要がある最大の問題の1つは、既知の入力に基づいて、イベントの特定の順序を正当化する機能です。たとえば、ユーザーがウィンドウのサイズを変更した場合、イベントキューモデルはイベントの特定の順序が発生することを保証しますが、これは単純に思えるかもしれませんが、キューが他のスレッドによるイベントのトリガーを許可した場合、順序を保証できなくなります。イベントが発生する可能性があり(競合状態)、突然、さまざまな状態について心配し始め、何かが起こるまで何かをしないで、状態フラグを共有しなければならず、スパゲッティになってしまいます。
さて、イベントが発行された時間に基づいてイベントを並べ替えるある種のキューを使用してこれを解決するかもしれませんが、それはすでに持っているものではありませんか?さらに、スレッドBがスレッドAの後にイベントを生成することを保証することはできません。
人々が自分のコードについて考えなければならないことに腹を立てる主な理由は、彼らが自分のコード/デザインについて考えさせられるからです。 「どうしてもっと簡単にできないの?」単純な問題ではないので、単純にすることはできません。
PS3がリリースされたとき、ソニーがCellプロセッサと話していたときのことを覚えています。それは、ロジックの別々の行を実行し、オーディオ、ビデオをデコードし、モデルデータをロードして解決する機能です。あるゲーム開発者は、「それはすべて素晴らしいですが、ストリームをどのように同期させますか?」と尋ねました。
開発者が話していた問題は、ある時点で、これらすべての個別のストリームを出力用に単一のパイプに同期させる必要があるということでした。貧しいプレゼンターは、慣れ親しんだ問題ではなかったため、肩をすくめました。明らかに、彼らは今この問題を解決するための解決策を持っていますが、それは当時面白いです。
現代のコンピューターはさまざまな場所から同時に多くの入力を取得しており、そのすべての入力は処理されて離れた場所にあるユーザーに配信される必要があるため、他の情報の表示に干渉せず、複雑な問題が発生します単一のシンプルなソリューション。
今、フレームワークを切り替える機能がありますが、設計は簡単ではありませんが、少しの間MVCを使用すると、MVCを多層にすることができます。つまり、UIフレームワークの管理を直接扱うMVCを使用できます。次に、他の(マルチスレッドの可能性がある)フレームワークとの相互作用を処理する上位レイヤーMVCで再度ラップすることができます。下位MVCレイヤーに通知/更新する方法を決定するのはこのレイヤーの責任です。
次に、コーディングを使用して、設計パターンとファクトリーまたはビルダーパターンをインターフェイスさせ、これらの異なるレイヤーを構築します。つまり、マルチスレッドフレームワークは、中間層を使用することで、UI層から切り離されることになります。