JavaFX के लिए थ्रेडिंग नियम
थ्रेड्स और JavaFX के लिए दो बुनियादी नियम हैं:
- कोई भी कोड जो दृश्य ग्राफ़ का हिस्सा नोड की स्थिति को संशोधित या एक्सेस करता है जरूरी JavaFX एप्लिकेशन थ्रेड पर निष्पादित किया जाएगा। कुछ अन्य ऑपरेशन (उदाहरण के लिए नया
स्टेज
creating बनाना s) भी इस नियम से बंधे हैं। - कोई भी कोड जिसे चलने में लंबा समय लग सकता है चाहिए पृष्ठभूमि थ्रेड पर निष्पादित किया जाना चाहिए (अर्थात FX एप्लिकेशन थ्रेड पर नहीं)।
पहले नियम का कारण यह है कि, अधिकांश UI टूलकिट की तरह, दृश्य ग्राफ़ के तत्वों की स्थिति पर बिना किसी सिंक्रोनाइज़ेशन के फ्रेमवर्क लिखा जाता है। सिंक्रोनाइज़ेशन जोड़ने पर एक प्रदर्शन लागत लगती है, और यह UI टूलकिट के लिए एक निषेधात्मक लागत साबित होती है। इस प्रकार केवल एक धागा ही सुरक्षित रूप से इस स्थिति तक पहुंच सकता है। चूंकि यूआई थ्रेड (जावाएफएक्स के लिए एफएक्स एप्लीकेशन थ्रेड) को दृश्य प्रस्तुत करने के लिए इस स्थिति तक पहुंचने की आवश्यकता है, एफएक्स एप्लीकेशन थ्रेड एकमात्र ऐसा धागा है जिस पर आप "लाइव" दृश्य ग्राफ स्थिति तक पहुंच सकते हैं। JavaFX 8 और बाद में, इस नियम के अधीन अधिकांश विधियाँ जाँच करती हैं और नियम का उल्लंघन होने पर रनटाइम अपवादों को फेंक देती हैं। (यह स्विंग के विपरीत है, जहां आप "अवैध" कोड लिख सकते हैं और यह ठीक चलता प्रतीत हो सकता है, लेकिन वास्तव में मनमाने समय पर यादृच्छिक और अप्रत्याशित विफलता की संभावना है।) यही कारण है IllegalStateException
आप देख रहे हैं :आप कॉल कर रहे हैं courseCodeLbl.setText(...)
FX एप्लिकेशन थ्रेड के अलावा किसी अन्य थ्रेड से।
दूसरे नियम का कारण यह है कि एफएक्स एप्लीकेशन थ्रेड, साथ ही उपयोगकर्ता घटनाओं को संसाधित करने के लिए जिम्मेदार होने के कारण, दृश्य को प्रस्तुत करने के लिए भी जिम्मेदार है। इस प्रकार यदि आप उस थ्रेड पर लंबे समय तक चलने वाला ऑपरेशन करते हैं, तो यूआई तब तक प्रस्तुत नहीं किया जाएगा जब तक कि वह ऑपरेशन पूरा न हो जाए, और उपयोगकर्ता ईवेंट के लिए अनुत्तरदायी हो जाएगा। हालांकि यह अपवाद उत्पन्न नहीं करेगा या भ्रष्ट वस्तु स्थिति का कारण नहीं बनेगा (जैसा कि नियम 1 वसीयत का उल्लंघन है), यह (सर्वोत्तम रूप से) खराब उपयोगकर्ता अनुभव बनाता है।
इस प्रकार यदि आपके पास एक लंबे समय तक चलने वाला ऑपरेशन है (जैसे डेटाबेस तक पहुंचना) जिसे पूरा होने पर यूआई को अपडेट करने की आवश्यकता होती है, तो मूल योजना पृष्ठभूमि थ्रेड में लंबे समय तक चलने वाले ऑपरेशन को निष्पादित करना है, जब ऑपरेशन के परिणाम वापस आते हैं पूर्ण करें, और फिर UI (FX एप्लिकेशन) थ्रेड पर UI के लिए एक अपडेट शेड्यूल करें। सभी सिंगल-थ्रेडेड UI टूलकिट में ऐसा करने के लिए एक तंत्र है:JavaFX में आप Platform.runLater(Runnable r)
पर कॉल करके ऐसा कर सकते हैं। निष्पादित करने के लिए r.run()
FX एप्लिकेशन थ्रेड पर। (स्विंग में, आप SwingUtilities.invokeLater(Runnable r)
पर कॉल कर सकते हैं। निष्पादित करने के लिए r.run()
AWT ईवेंट डिस्पैच थ्रेड पर।) JavaFX (इस उत्तर में बाद में देखें) FX एप्लिकेशन थ्रेड पर संचार को वापस प्रबंधित करने के लिए कुछ उच्च-स्तरीय API भी प्रदान करता है।
मल्टीथ्रेडिंग के लिए सामान्य अच्छे अभ्यास
एकाधिक थ्रेड्स के साथ काम करने के लिए सबसे अच्छा अभ्यास कोड की संरचना करना है जिसे "उपयोगकर्ता-परिभाषित" थ्रेड पर एक ऑब्जेक्ट के रूप में निष्पादित किया जाना है जिसे कुछ निश्चित स्थिति के साथ प्रारंभ किया गया है, जिसमें ऑपरेशन करने की एक विधि है, और पूरा होने पर एक ऑब्जेक्ट देता है परिणाम का प्रतिनिधित्व। आरंभिक अवस्था और गणना परिणाम के लिए अपरिवर्तनीय वस्तुओं का उपयोग करना अत्यधिक वांछनीय है। यहाँ विचार यह है कि जहाँ तक संभव हो, किसी भी परिवर्तनशील अवस्था के कई धागों से दिखाई देने की संभावना को समाप्त किया जाए। डेटाबेस से डेटा एक्सेस करना इस मुहावरे को अच्छी तरह से फिट करता है:आप डेटाबेस एक्सेस (खोज शब्द, आदि) के पैरामीटर के साथ अपने "कार्यकर्ता" ऑब्जेक्ट को प्रारंभ कर सकते हैं। डेटाबेस क्वेरी निष्पादित करें और परिणाम सेट प्राप्त करें, डोमेन ऑब्जेक्ट के संग्रह को पॉप्युलेट करने के लिए परिणाम सेट का उपयोग करें, और संग्रह को अंत में वापस करें।
कुछ मामलों में कई धागों के बीच परिवर्तनशील स्थिति साझा करना आवश्यक होगा। जब यह पूरी तरह से किया जाना है, तो आपको राज्य को असंगत स्थिति में देखने से बचने के लिए उस राज्य तक पहुंच को सावधानीपूर्वक सिंक्रनाइज़ करने की आवश्यकता है (ऐसे अन्य सूक्ष्म मुद्दे हैं जिन्हें संबोधित करने की आवश्यकता है, जैसे कि राज्य की जीवंतता, आदि)। जब इसकी आवश्यकता हो तो मजबूत अनुशंसा यह है कि आपके लिए इन जटिलताओं को प्रबंधित करने के लिए एक उच्च-स्तरीय पुस्तकालय का उपयोग किया जाए।
javafx.concurrent API का उपयोग करना
JavaFX एक concurrency API
प्रदान करता है। यह एक पृष्ठभूमि थ्रेड में कोड निष्पादित करने के लिए डिज़ाइन किया गया है, एपीआई के साथ विशेष रूप से उस कोड के निष्पादन के पूरा होने पर (या उसके दौरान) JavaFX UI को अपडेट करने के लिए डिज़ाइन किया गया है। इस एपीआई को java.util.concurrent
एपीआई
, जो मल्टीथ्रेडेड कोड लिखने के लिए सामान्य सुविधाएं प्रदान करता है (लेकिन UI हुक के बिना)। javafx.concurrent
. में प्रमुख वर्ग है कार्य
, जो एक एकल, एकबारगी, कार्य की इकाई का प्रतिनिधित्व करता है जिसे पृष्ठभूमि थ्रेड पर निष्पादित करने का इरादा है। यह वर्ग एकल अमूर्त विधि को परिभाषित करता है, call()
, जो कोई पैरामीटर नहीं लेता है, परिणाम देता है, और चेक किए गए अपवादों को फेंक सकता है। कार्य
चलाने योग्य
. को लागू करता है इसके run()
. के साथ विधि केवल कॉल ()
का आह्वान कर रही है . कार्य
एफएक्स एप्लिकेशन थ्रेड पर स्थिति को अपडेट करने की गारंटी देने वाली विधियों का एक संग्रह भी है, जैसे कि updateProgress(...)
, अपडेटमैसेज(...)
, आदि। यह कुछ अवलोकन योग्य गुणों को परिभाषित करता है (जैसे राज्य
और value
कोड>
):इन गुणों के श्रोताओं को FX एप्लिकेशन थ्रेड पर परिवर्तनों के बारे में सूचित किया जाएगा। अंत में, हैंडलर पंजीकृत करने के लिए कुछ सुविधाजनक तरीके हैं (setOnSucceeded(...)
, setOnFailed(...)
, आदि); इन विधियों के माध्यम से पंजीकृत किसी भी हैंडलर को FX एप्लिकेशन थ्रेड पर भी आमंत्रित किया जाएगा।
तो डेटाबेस से डेटा पुनर्प्राप्त करने का सामान्य सूत्र है:
- एक
कार्य बनाएं
डेटाबेस में कॉल को संभालने के लिए। कार्य प्रारंभ करें
डेटाबेस कॉल करने के लिए आवश्यक किसी भी राज्य के साथ।- कार्य के
कॉल ()
को लागू करें डेटाबेस कॉल करने की विधि, कॉल के परिणाम लौटाते हुए। - कार्य पूरा होने पर UI को परिणाम भेजने के लिए कार्य के साथ एक हैंडलर पंजीकृत करें।
- कार्य को पृष्ठभूमि थ्रेड पर आमंत्रित करें।
डेटाबेस एक्सेस के लिए, मैं एक अलग वर्ग में वास्तविक डेटाबेस कोड को एनकैप्सुलेट करने की दृढ़ता से अनुशंसा करता हूं जो UI के बारे में कुछ नहीं जानता ( डेटा एक्सेस ऑब्जेक्ट डिज़ाइन पैटर्न ) फिर बस कार्य डेटा एक्सेस ऑब्जेक्ट पर विधियों का आह्वान करें।
तो आपके पास इस तरह एक डीएओ वर्ग हो सकता है (ध्यान दें कि यहां कोई यूआई कोड नहीं है):
public class WidgetDAO {
// In real life, you might want a connection pool here, though for
// desktop applications a single connection often suffices:
private Connection conn ;
public WidgetDAO() throws Exception {
conn = ... ; // initialize connection (or connection pool...)
}
public List<Widget> getWidgetsByType(String type) throws SQLException {
try (PreparedStatement pstmt = conn.prepareStatement("select * from widget where type = ?")) {
pstmt.setString(1, type);
ResultSet rs = pstmt.executeQuery();
List<Widget> widgets = new ArrayList<>();
while (rs.next()) {
Widget widget = new Widget();
widget.setName(rs.getString("name"));
widget.setNumberOfBigRedButtons(rs.getString("btnCount"));
// ...
widgets.add(widget);
}
return widgets ;
}
}
// ...
public void shutdown() throws Exception {
conn.close();
}
}
विजेट्स का एक गुच्छा पुनर्प्राप्त करने में काफी समय लग सकता है, इसलिए यूआई क्लास (उदाहरण के लिए नियंत्रक वर्ग) से किसी भी कॉल को इसे पृष्ठभूमि थ्रेड पर शेड्यूल करना चाहिए। एक नियंत्रक वर्ग इस तरह दिख सकता है:
public class MyController {
private WidgetDAO widgetAccessor ;
// java.util.concurrent.Executor typically provides a pool of threads...
private Executor exec ;
@FXML
private TextField widgetTypeSearchField ;
@FXML
private TableView<Widget> widgetTable ;
public void initialize() throws Exception {
widgetAccessor = new WidgetDAO();
// create executor that uses daemon threads:
exec = Executors.newCachedThreadPool(runnable -> {
Thread t = new Thread(runnable);
t.setDaemon(true);
return t ;
});
}
// handle search button:
@FXML
public void searchWidgets() {
final String searchString = widgetTypeSearchField.getText();
Task<List<Widget>> widgetSearchTask = new Task<List<Widget>>() {
@Override
public List<Widget> call() throws Exception {
return widgetAccessor.getWidgetsByType(searchString);
}
};
widgetSearchTask.setOnFailed(e -> {
widgetSearchTask.getException().printStackTrace();
// inform user of error...
});
widgetSearchTask.setOnSucceeded(e ->
// Task.getValue() gives the value returned from call()...
widgetTable.getItems().setAll(widgetSearchTask.getValue()));
// run the task using a thread from the thread pool:
exec.execute(widgetSearchTask);
}
// ...
}
ध्यान दें कि (संभावित रूप से) लंबे समय तक चलने वाली डीएओ विधि को कॉल कार्य
में कैसे लपेटा जाता है जो UI (उपरोक्त नियम 2) को अवरुद्ध करने से रोकने के लिए पृष्ठभूमि थ्रेड (एक्सेसर के माध्यम से) पर चलाया जाता है। UI का अद्यतन (widgetTable.setItems(...)
) वास्तव में कार्य
. का उपयोग करके FX एप्लिकेशन थ्रेड पर वापस निष्पादित किया जाता है की सुविधा कॉलबैक विधि setOnSucceeded(...)
(संतोषजनक नियम 1)।
आपके मामले में, आप जिस डेटाबेस एक्सेस का प्रदर्शन कर रहे हैं, वह एक ही परिणाम देता है, इसलिए आपके पास एक विधि हो सकती है जैसे
public class MyDAO {
private Connection conn ;
// constructor etc...
public Course getCourseByCode(int code) throws SQLException {
try (PreparedStatement pstmt = conn.prepareStatement("select * from course where c_code = ?")) {
pstmt.setInt(1, code);
ResultSet results = pstmt.executeQuery();
if (results.next()) {
Course course = new Course();
course.setName(results.getString("c_name"));
// etc...
return course ;
} else {
// maybe throw an exception if you want to insist course with given code exists
// or consider using Optional<Course>...
return null ;
}
}
}
// ...
}
और फिर आपका कंट्रोलर कोड कुछ इस तरह दिखेगा
final int courseCode = Integer.valueOf(courseId.getText());
Task<Course> courseTask = new Task<Course>() {
@Override
public Course call() throws Exception {
return myDAO.getCourseByCode(courseCode);
}
};
courseTask.setOnSucceeded(e -> {
Course course = courseTask.getCourse();
if (course != null) {
courseCodeLbl.setText(course.getName());
}
});
exec.execute(courseTask);
कार्यकोड के लिए API दस्तावेज़>
प्रगति
. को अपडेट करने सहित कई और उदाहरण हैं कार्य की संपत्ति (प्रगति सलाखों के लिए उपयोगी..., आदि.