खास जानकारी
स्काईफ़्रेम StateMachine
एक बनाया गया फ़ंक्शन-ऑब्जेक्ट है जो हीप पर मौजूद होता है. जब ज़रूरी वैल्यू तुरंत उपलब्ध नहीं होती हैं और उन्हें एसिंक्रोनस तरीके से कंप्यूट किया जाता है, तो रिडंडंसी1 के बिना इस सुविधा का इस्तेमाल किया जा सकता है. इंतज़ार
करने के दौरान StateMachine
, थ्रेड रिसॉर्स को टाई नहीं कर सकता. इसके बजाय,
उसे निलंबित करके फिर से शुरू करना होगा. इस तरह डिकंपोज़िशन में साफ़ तौर पर फिर से एंट्री की गई होती है, ताकि पहले की कंप्यूटेशन को रोका जा सके.
StateMachine
का इस्तेमाल क्रम, ब्रांचिंग, स्ट्रक्चर्ड लॉजिकल कॉनकरेंसी को दिखाने के लिए किया जा सकता है. साथ ही, इन्हें खास तौर पर स्काईफ़्रेम इंटरैक्शन के लिए तैयार किया जाता है.
StateMachine
को बड़े StateMachine
में जोड़ा जा सकता है और सब-StateMachine
शेयर किया जा सकता है. कॉनकरेंसी हमेशा निर्माण के आधार पर हैरारकी है और पूरी तरह से तार्किक है. हर कंबाइंड सबटास्क एक ही शेयर किए गए पैरंट में काम करता है
SkyFunction थ्रेड में.
शुरुआती जानकारी
इस सेक्शन में, कम शब्दों में StateMachine
के बारे में जानकारी दी गई है और इसके बारे में बताया गया है. यह जानकारी
java.com.google.devtools.build.skyframe.state
पैकेज में दी गई है.
Skyframe के बारे में कम शब्दों में जानकारी फिर से शुरू हो रही है
स्काईफ़्रेम एक ऐसा फ़्रेमवर्क है जो डिपेंडेंसी ग्राफ़ को पैरलल इवैलुएशन करता है.
ग्राफ़ में मौजूद हर नोड, स्काई फ़ंक्शन के मूल्यांकन से मेल खाता है.
इसके लिए, स्काईकी के पैरामीटर और स्काईवैल्यू से उसके नतीजों की जानकारी मिलती है. कंप्यूटेशनल मॉडल इस तरह काम करता है कि स्काईफ़ंक्शन, SkyKey के मुताबिक SkyValues को देख सकता है. साथ ही, अन्य स्काईफ़ंक्शन का बार-बार होने वाला इवैलुएशन भी ट्रिगर कर सकता है. ब्लॉक करने के बजाय, थ्रेड के साथ बंधा होना, जब स्काईवैल्यू का अनुरोध किया गया अब तक तैयार नहीं है, क्योंकि कंप्यूटेशन का कुछ सब-ग्राफ़ अधूरा है, अनुरोध करने वाले स्काई फ़ंक्शन, null
getValue
रिस्पॉन्स देखते हैं और स्काईवैल्यू के बजाय, null
को रिटर्न करते हैं. इससे, यह पता चलता है कि इनपुट मौजूद नहीं हैं.
जब स्काईवैल्यू का पहले से अनुरोध किया जा चुका होता है, तो स्काईफ़्रेम, SkyFunction को फिर से शुरू करता है.
SkyKeyComputeState
के आने से पहले, कंप्यूटेशन को पूरी तरह फिर से तैयार करके
रीस्टार्ट करें. हालांकि, इस तरीके को क्वाड्रेटिक
की वजह से मुश्किल बनाया गया है, लेकिन इस तरीके से लिखा गया फ़ंक्शन हर बार पूरा होता है, क्योंकि हर नतीजे को फिर से चलाने पर,
कम नतीजे मिलते हैं null
. SkyKeyComputeState
के साथ, हाथ से तय किए गए चेक-पॉइंट डेटा को SkyFunction के साथ जोड़ा जा सकता है, जिससे अहम कंप्यूटेशन की बचत होती है.
StateMachine
वे ऑब्जेक्ट हैं जो SkyKeyComputeState
में मौजूद होते हैं. साथ ही, SkyFunction के फिर से शुरू होने पर (यह मानते हुए कि SkyKeyComputeState
कैश मेमोरी में सेव नहीं होता है) वर्चुअल तरीके से सभी कंप्यूटेशन को खत्म कर देता है. इसके लिए, निलंबन और फिर से शुरू करने के लिए हुक का इस्तेमाल किया जाता है.
SkyKeyComputeState
में कंप्यूट कंप्यूटिंग की सुविधा
ऑब्जेक्ट के मुताबिक डिज़ाइन वाले डिज़ाइन से, कंप्यूटेशनल ऑब्जेक्ट को शुद्ध डेटा वैल्यू के बजाय SkyKeyComputeState
में स्टोर किया जा सकता है.
Java में, किसी ऑब्जेक्ट को ले जाने वाले व्यवहार का कम से कम ब्यौरा फ़ंक्शनल इंटरफ़ेस है और यह काफ़ी है. StateMachine
में ये चीज़ें हैं, जो दिलचस्प हैं और बार-बार दोहराए जाने वाले हैं. 2.
@FunctionalInterface
public interface StateMachine {
StateMachine step(Tasks tasks) throws InterruptedException;
}
Tasks
इंटरफ़ेस, SkyFunction.Environment
से मिलता-जुलता है, लेकिन इसे एसिंक्रोनस के लिए डिज़ाइन किया गया है. साथ ही, यह तार्किक तौर पर एक साथ काम करने वाले सबटास्क के लिए काम करता है3.
step
की रिटर्न वैल्यू एक और StateMachine
है, जो स्टेप के क्रम के बारे में जानकारी देती है. step
पूरा होने पर DONE
StateMachine
दिखाता है. उदाहरण के लिए:
class HelloWorld implements StateMachine {
@Override
public StateMachine step(Tasks tasks) {
System.out.println("hello");
return this::step2; // The next step is HelloWorld.step2.
}
private StateMachine step2(Tasks tasks) {
System.out.println("world");
// DONE is special value defined in the `StateMachine` interface signaling
// that the computation is done.
return DONE;
}
}
StateMachine
के बारे में इस आउटपुट से बताता है.
hello
world
ध्यान दें कि step2
के StateMachine
के फ़ंक्शनल इंटरफ़ेस की परिभाषा पूरी होने की वजह से, तरीका रेफ़रंस this::step2
भी StateMachine
है. मैथड रेफ़रंस में, StateMachine
में अगली स्थिति के बारे में बताने का सबसे आम तरीका है.
आसानी से कंप्यूटेशन को किसी एक फ़ंक्शन के बजाय StateMachine
चरणों में बांट देने से, एक कंप्यूटेशन निलंबित और फिर से शुरू हो जाता है. जब StateMachine.step
वापस आता है, तो साफ़ तौर पर एक निलंबित पॉइंट दिखता है. रिटर्न के लिए तय वैल्यू के आधार पर StateMachine
की वैल्यू Resume का पॉइंट होता है. इसलिए, कंप्यूटेशन से बचा जा सकता है, क्योंकि कंप्यूटेशन को वहीं से शुरू किया जा सकता है जहां उसे छोड़ा गया था.
कॉलबैक, कंटिन्युलेशन, और एसिंक्रोनस कंप्यूटेशन
तकनीकी भाषा में, StateMachine
एक जारी रखने का काम करता है, जो बाद में की जाने वाली गणना का आकलन करता है. ब्लॉक करने के बजाय, StateMachine
अपने-आप step
फ़ंक्शन से वापस आकर निलंबित कर सकता है, जो कंट्रोल को वापस Driver
इंस्टेंस में ट्रांसफ़र कर देता है. इसके बाद, Driver
तैयार StateMachine
पर स्विच किया जा सकता है या कंट्रोल को फिर से स्काईफ़्रेम में बदला जा सकता है.
आम तौर पर, कॉलबैक और जारी रखने की सुविधा को एक ही कॉन्सेप्ट में शामिल किया जाता है.
StateMachine
, दोनों के बीच अंतर बताते हैं.
- कॉलबैक - यह बताता है कि एसिंक्रोनस गणना का नतीजा कहां सेव करना है.
- जारी रखना - एक्ज़ीक्यूशन की अगली स्थिति के बारे में बताता है.
एसिंक्रोनस ऑपरेशन का इस्तेमाल करते समय कॉलबैक की ज़रूरत होती है. इसका मतलब है कि तरीके को कॉल करने पर असल कार्रवाई नहीं होती, जैसा कि SkyValue लुकअप में होता है. कॉलबैक को जितना हो सके उतना आसान रखें.
जारी रखने में, StateMachine
के StateMachine
रिटर्न की वैल्यू होती हैं. साथ ही, मुश्किल प्रक्रिया को एक साथ लागू किया जाता है. इस प्रोसेस के दौरान, सभी एसिंक्रोनस
कंप्यूट का समाधान हो जाता है. इस स्ट्रक्चर्ड तरीके से, कॉलबैक को आसान बनाया जा सकता है.
टास्क
Tasks
इंटरफ़ेस की मदद से, StateMachine
को SkyKeys के ज़रिए SkyValues
को खोजने और एक साथ सबटास्क को शेड्यूल करने की सुविधा मिलती है.
interface Tasks {
void enqueue(StateMachine subtask);
void lookUp(SkyKey key, Consumer<SkyValue> sink);
<E extends Exception>
void lookUp(SkyKey key, Class<E> exceptionClass, ValueOrExceptionSink<E> sink);
// lookUp overloads for 2 and 3 exception types exist, but are elided here.
}
SkyValue लुकअप
StateMachine
SkyValues का पता लगाने के लिए Tasks.lookUp
ओवरलोड का इस्तेमाल करता है. ये SkyFunction.Environment.getValue
और
SkyFunction.Environment.getValueOrThrow
की भाषा से मेल खाते हैं. साथ ही, इनमें अपवाद के तौर पर एक जैसे सिंटैक्स होते हैं. लागू करने से तुरंत लुकअप नहीं होता है, बल्कि यह करने से पहले4 ज़्यादा से ज़्यादा लुकअप करता है. हो सकता है कि वैल्यू तुरंत उपलब्ध न हो,
जैसे कि स्काईफ़्रेम को रीस्टार्ट करना ज़रूरी हो, ताकि कॉलर कॉलबैक से मिलने वाली नतीजे के साथ क्या करे.
StateMachine
प्रोसेसर (Driver
s और
SkyFrame में कनेक्ट होने की सुविधा) यह गारंटी देता है कि अगली स्थिति शुरू होने से पहले, वैल्यू उपलब्ध हो जाएगी. एक उदाहरण नीचे दिखाया गया है.
class DoesLookup implements StateMachine, Consumer<SkyValue> {
private Value value;
@Override
public StateMachine step(Tasks tasks) {
tasks.lookUp(new Key(), (Consumer<SkyValue>) this);
return this::processValue;
}
// The `lookUp` call in `step` causes this to be called before `processValue`.
@Override // Implementation of Consumer<SkyValue>.
public void accept(SkyValue value) {
this.value = (Value)value;
}
private StateMachine processValue(Tasks tasks) {
System.out.println(value); // Prints the string representation of `value`.
return DONE;
}
}
ऊपर दिए गए उदाहरण में, पहला चरण new Key()
को new Key()
के तौर पर देखता है, जो उपभोक्ता के तौर पर this
पास करता है. ऐसा इसलिए है, क्योंकि DoesLookup
Consumer<SkyValue>
लागू होता है.
समझौते के मुताबिक, अगली स्थिति DoesLookup.processValue
शुरू होने से पहले, DoesLookup.step
के सभी लुकअप पूरे हो जाते हैं. इसलिए, processValue
में इसे ऐक्सेस करने पर
value
उपलब्ध है.
सबटास्क
Tasks.enqueue
तार्किक रूप से एक साथ किए जाने वाले सबटास्क के काम का अनुरोध करता है.
सबटास्क StateMachine
भी हैं और ये नियमित तौर पर StateMachine
जैसे कुछ भी कर सकते हैं. इसमें बार-बार ज़्यादा सबटास्क बनाना और SkyValues को देखना शामिल है.
lookUp
की तरह ही, राज्य मशीन ड्राइवर यह पक्का करता है कि सभी अगले टास्क पूरे होने से पहले सभी टास्क पूरे हो जाएं. एक उदाहरण नीचे दिखाया गया है.
class Subtasks implements StateMachine {
private int i = 0;
@Override
public StateMachine step(Tasks tasks) {
tasks.enqueue(new Subtask1());
tasks.enqueue(new Subtask2());
// The next step is Subtasks.processResults. It won't be called until both
// Subtask1 and Subtask 2 are complete.
return this::processResults;
}
private StateMachine processResults(Tasks tasks) {
System.out.println(i); // Prints "3".
return DONE; // Subtasks is done.
}
private class Subtask1 implements StateMachine {
@Override
public StateMachine step(Tasks tasks) {
i += 1;
return DONE; // Subtask1 is done.
}
}
private class Subtask2 implements StateMachine {
@Override
public StateMachine step(Tasks tasks) {
i += 2;
return DONE; // Subtask2 is done.
}
}
}
Subtask1
और Subtask2
तार्किक रूप से एक साथ होते हैं. हालांकि, सब कुछ एक ही थ्रेड में चलता है. इसलिए, i
के "एक साथ" अपडेट करने के लिए, सिंक्रोनाइज़ेशन की कोई ज़रूरत नहीं होती.
स्ट्रक्चर्ड कॉनकरेंसी
हर lookUp
और enqueue
को अगली स्थिति में जाने से पहले समाधान करना होगा. इसका मतलब है कि एक साथ कई पेड़, स्ट्रक्चर तक सीमित हैं. जैसा कि नीचे दिए गए उदाहरण में दिखाया गया है, हो सकता है कि हैरारकी 5 को एक साथ बनाया जा सके.
UML से यह कहना मुश्किल है कि एक साथ कई काम करने की संरचना एक पेड़ बनाती है. इसमें एक वैकल्पिक व्यू है जो पेड़ के स्ट्रक्चर को बेहतर तरीके से दिखाता है.
स्ट्रक्चर्ड कॉनकरेंसी को आसानी से समझा जा सकता है.
कंपोज़िशन और कंट्रोल फ़्लो के पैटर्न
इस सेक्शन में उदाहरण देकर बताया गया है कि एक से ज़्यादा StateMachine
को कैसे बनाया जा सकता है. साथ ही, कुछ खास कंट्रोल फ़्लो की समस्याओं को कैसे ठीक किया जा सकता है.
क्रम में चलने वाली स्थितियां
यह सबसे आम और आसान कंट्रोल फ़्लो पैटर्न है. इसका एक उदाहरण
SkyKeyComputeState
में स्टेट कंप्यूटेशन में दिखाया गया है.
ब्रांचिंग
यहां दिए गए उदाहरण के मुताबिक, सामान्य Java कंट्रोल फ़्लो का इस्तेमाल करके, अलग-अलग वैल्यू डालकर StateMachine
में ब्रांचिंग स्थितियां हासिल की जा सकती हैं.
class Branch implements StateMachine {
@Override
public StateMachine step(Tasks tasks) {
// Returns different state machines, depending on condition.
if (shouldUseA()) {
return this::performA;
}
return this::performB;
}
…
}
कुछ ब्रांच के लिए, DONE
जल्दी लौटाना ज़रूरी होता है.
बेहतर अनुक्रमिक कंपोज़िशन
StateMachine
कंट्रोल स्ट्रक्चर में मेमोरी नहीं होती है. इसलिए, StateMachine
टास्क के तौर पर परिभाषाएं शेयर करना मुश्किल हो सकता है. मान लें कि M1 और
M2 StateMachine
वाले S, को M1 और
M2 के साथ, क्रम में <A, S, B> और
<X, S, Y> इस्तेमाल करते हैं.StateMachine
समस्या यह है कि S को पता नहीं है कि
B या Y के Keep और StateMachine
s के बनाए रखने तक, जारी रखना है या नहीं. इस सेक्शन में, कुछ तरीकों को हासिल किया गया है.
टर्मिनल सिक्वेंस एलिमेंट के तौर पर StateMachine
इससे शुरू की गई समस्या हल नहीं होती है. यह सिर्फ़ क्रम में लगाए गए कंपोज़िशन को दिखाता है, जब शेयर किए गए StateMachine
को क्रम में रखा जाता है.
// S is the shared state machine.
class S implements StateMachine { … }
class M1 implements StateMachine {
@Override
public StateMachine step(Tasks tasks) {
performA();
return new S();
}
}
class M2 implements StateMachine {
@Override
public StateMachine step(Tasks tasks) {
performX();
return new S();
}
}
यह तब भी काम करता है, जब S खुद एक कॉम्प्लेक्स स्टेट मशीन हो.
क्रम में चलने वाले कंपोज़िशन के लिए सबटास्क
इस बात की गारंटी है कि कतार में रखे गए सबटास्क अगली स्थिति से पहले पूरे हो जाएंगे. इसलिए, कभी-कभी सबटास्क 6 सब-टाइप का गलत इस्तेमाल हो सकता है.
class M1 implements StateMachine {
@Override
public StateMachine step(Tasks tasks) {
performA();
// S starts after `step` returns and by contract must complete before `doB`
// begins. It is effectively sequential, inducing the sequence < A, S, B >.
tasks.enqueue(new S());
return this::doB;
}
private StateMachine doB(Tasks tasks) {
performB();
return DONE;
}
}
class M2 implements StateMachine {
@Override
public StateMachine step(Tasks tasks) {
performX();
// Similarly, this induces the sequence < X, S, Y>.
tasks.enqueue(new S());
return this::doY;
}
private StateMachine doY(Tasks tasks) {
performY();
return DONE;
}
}
runAfter
इंजेक्शन
कभी-कभी, Tasks.enqueue
का गलत इस्तेमाल नहीं किया जा सकता. ऐसा इसलिए,
क्योंकि करीब-करीब सबटास्क हैं या Tasks.lookUp
कॉल S एक्ज़ीक्यूट होने से पहले पूरे किए जाने चाहिए. इस मामले में, S में runAfter
पैरामीटर डालने का इस्तेमाल, आगे की कार्रवाई के लिए S को बताने के लिए किया जा सकता है.
class S implements StateMachine {
// Specifies what to run after S completes.
private final StateMachine runAfter;
@Override
public StateMachine step(Tasks tasks) {
… // Performs some computations.
return this::processResults;
}
@Nullable
private StateMachine processResults(Tasks tasks) {
… // Does some additional processing.
// Executes the state machine defined by `runAfter` after S completes.
return runAfter;
}
}
class M1 implements StateMachine {
@Override
public StateMachine step(Tasks tasks) {
performA();
// Passes `this::doB` as the `runAfter` parameter of S, resulting in the
// sequence < A, S, B >.
return new S(/* runAfter= */ this::doB);
}
private StateMachine doB(Tasks tasks) {
performB();
return DONE;
}
}
class M2 implements StateMachine {
@Override
public StateMachine step(Tasks tasks) {
performX();
// Passes `this::doY` as the `runAfter` parameter of S, resulting in the
// sequence < X, S, Y >.
return new S(/* runAfter= */ this::doY);
}
private StateMachine doY(Tasks tasks) {
performY();
return DONE;
}
}
यह तरीका, सबटास्क का गलत इस्तेमाल करने से ज़्यादा साफ़ है. हालांकि, StateMachine
को अपने हिसाब से लागू करने का यह उदाहरण है कि एक से ज़्यादा StateMachine
को runAfter
के साथ नेस्ट करके, कॉलबैक हेल बनाया जा सकता है. इसके बजाय, क्रम में चलने वाली स्थितियों के
runAfter
क्रम को अलग-अलग रखना बेहतर है.
return new S(/* runAfter= */ new T(/* runAfter= */ this::nextStep))
इन्हें नीचे दी गई जगह से बदला जा सकता है.
private StateMachine step1(Tasks tasks) {
doStep1();
return new S(/* runAfter= */ this::intermediateStep);
}
private StateMachine intermediateStep(Tasks tasks) {
return new T(/* runAfter= */ this::nextStep);
}
मना है विकल्प: runAfterUnlessError
एक पुराने ड्राफ़्ट में, हमने runAfterUnlessError
की समीक्षा की थी जो गड़बड़ियों की शुरुआत में ही रद्द कर दी जाएगी. उपयोगकर्ताओं को डेटा इकट्ठा करने की प्रेरणा इसी वजह से मिली. अक्सर गड़बड़ियों की जांच दो बार होती है. एक बार runAfter
से जुड़ी StateMachine
समस्या होती है और runAfter
मशीन से एक बार.
कुछ विचार-विमर्श के बाद, हमने तय किया कि गड़बड़ी की जांच की डुप्लीकेट कॉपी करने के बजाय कोड का एक जैसा होना ज़्यादा ज़रूरी है. अगर runAfter
मैकेनिज़्म tasks.enqueue
मैकेनिज़्म के साथ काम नहीं करता है, तो इसे समझने में मुश्किल हो सकती है. इसके लिए हमेशा गड़बड़ी की जांच करने की ज़रूरत होती है.
प्रत्यक्ष प्रतिनिधि
हर बार औपचारिक स्थिति में बदलाव होने पर, मुख्य Driver
लूप आगे बढ़ जाता है.
समझौते के मुताबिक, आगे बढ़ने से पहले की स्थितियों से लगता है कि पहले से बनाए गए स्काईवैल्यू लुकअप और सबटास्क का अगली स्थिति में आने से पहले समाधान हो जाएगा. कभी-कभी, StateMachine
किसी और को काम के लिए इस्तेमाल करने का तर्क, चरण को आगे बढ़ाने के बजाय, ग़ैर-ज़रूरी या काम की बन जाता है. उदाहरण के लिए, अगर डेलिगेट का पहला step
SkyKey लुकअप का इस्तेमाल करता है जो डेलिगेट करने की स्थिति के लुकअप के बराबर हो सकता है, तो फ़ेज़ ऐडवांस में क्रम में चलने वाले लुकअप बन जाते हैं. जैसा कि नीचे दिए गए उदाहरण में दिखाया गया है, उन लोगों को सीधे तौर पर अपने ईमेल खाते का ऐक्सेस देना ज़्यादा सही होगा.
class Parent implements StateMachine {
@Override
public StateMachine step(Tasks tasks ) {
tasks.lookUp(new Key1(), this);
// Directly delegates to `Delegate`.
//
// The (valid) alternative:
// return new Delegate(this::afterDelegation);
// would cause `Delegate.step` to execute after `step` completes which would
// cause lookups of `Key1` and `Key2` to be sequential instead of parallel.
return new Delegate(this::afterDelegation).step(tasks);
}
private StateMachine afterDelegation(Tasks tasks) {
…
}
}
class Delegate implements StateMachine {
private final StateMachine runAfter;
Delegate(StateMachine runAfter) {
this.runAfter = runAfter;
}
@Override
public StateMachine step(Tasks tasks) {
tasks.lookUp(new Key2(), this);
return …;
}
// Rest of implementation.
…
private StateMachine complete(Tasks tasks) {
…
return runAfter;
}
}
डेटा फ़्लो
पिछली चर्चा का फ़ोकस कंट्रोल फ़्लो को मैनेज करने पर रहा था. यह सेक्शन, डेटा वैल्यू को लागू करने के बारे में बताता है.
Tasks.lookUp
कॉलबैक लागू किया जा रहा है
SkyValue
लुकअप में Tasks.lookUp
कॉलबैक लागू करने का एक उदाहरण है. इस सेक्शन में, एक से ज़्यादा स्काईवैल्यू को मैनेज करने की वजह बताई गई है.
Tasks.lookUp
कॉलबैक
Tasks.lookUp
का तरीका एक पैरामीटर है, sink
, जो पैरामीटर है.
void lookUp(SkyKey key, Consumer<SkyValue> sink);
मुहावरे वाला तरीका है, इसे लागू करने के लिए Java lambda का इस्तेमाल करना:
tasks.lookUp(key, value -> myValue = (MyValueClass)value);
myValue
होने पर, यह StateMachine
इंस्टेंस का सदस्य वैरिएबल है, जो लुकअप कर रहा है. हालांकि, Lambda फ़ंक्शन को लागू करने के लिए, StateMachine
इंटरफ़ेस में Consumer<SkyValue>
इंटरफ़ेस की तुलना में ज़्यादा मेमोरी असाइन करना ज़रूरी है. Lambda फ़ंक्शन अब भी तब काम आता है, जब कई लुकअप की वजह से साफ़ तौर पर कुछ समझ नहीं आता हो.
Tasks.lookUp
के ओवरलोड को हैंडल करने में भी गड़बड़ी हुई, जो
SkyFunction.Environment.getValueOrThrow
की तरह है.
<E extends Exception> void lookUp(
SkyKey key, Class<E> exceptionClass, ValueOrExceptionSink<E> sink);
interface ValueOrExceptionSink<E extends Exception> {
void acceptValueOrException(@Nullable SkyValue value, @Nullable E exception);
}
लागू करने का एक उदाहरण नीचे दिखाया गया है.
class PerformLookupWithError extends StateMachine, ValueOrExceptionSink<MyException> {
private MyValue value;
private MyException error;
@Override
public StateMachine step(Tasks tasks) {
tasks.lookUp(new MyKey(), MyException.class, ValueOrExceptionSink<MyException>) this);
return this::processResult;
}
@Override
public acceptValueOrException(@Nullable SkyValue value, @Nullable MyException exception) {
if (value != null) {
this.value = (MyValue)value;
return;
}
if (exception != null) {
this.error = exception;
return;
}
throw new IllegalArgumentException("Both parameters were unexpectedly null.");
}
private StateMachine processResult(Tasks tasks) {
if (exception != null) {
// Handles the error.
…
return DONE;
}
// Processes `value`, which is non-null.
…
}
}
बिना गड़बड़ी हैंडलिंग वाले लुकअप की तरह, StateMachine
क्लास को सीधे लागू करने पर कॉलबैक, Lamba के लिए मेमोरी को असाइन करता है.
गड़बड़ी को ठीक करना, थोड़ा और जानकारी देता है, लेकिन असल में, गड़बड़ी और सामान्य मानों के प्रसार में बहुत ज़्यादा अंतर नहीं होता है.
कई SkyValues का इस्तेमाल करना
अक्सर कई SkyValue लुकअप की ज़रूरत होती है. ज़्यादातर मामलों में, SkyValue का टाइप बदला जा सकता है. नीचे एक उदाहरण दिया गया है, जिसे प्रोटोटाइप प्रोडक्शन कोड से आसान बनाया गया है.
@Nullable
private StateMachine fetchConfigurationAndPackage(Tasks tasks) {
var configurationKey = configuredTarget.getConfigurationKey();
if (configurationKey != null) {
tasks.lookUp(configurationKey, (Consumer<SkyValue>) this);
}
var packageId = configuredTarget.getLabel().getPackageIdentifier();
tasks.lookUp(PackageValue.key(packageId), (Consumer<SkyValue>) this);
return this::constructResult;
}
@Override // Implementation of `Consumer<SkyValue>`.
public void accept(SkyValue value) {
if (value instanceof BuildConfigurationValue) {
this.configurationValue = (BuildConfigurationValue) value;
return;
}
if (value instanceof PackageValue) {
this.pkg = ((PackageValue) value).getPackage();
return;
}
throw new IllegalArgumentException("unexpected value: " + value);
}
Consumer<SkyValue>
कॉलबैक को अच्छी तरह लागू नहीं किया जा सकता, क्योंकि वैल्यू टाइप अलग-अलग होते हैं. अगर ऐसा नहीं है, तो Lambda पर आधारित काम करने के तरीके या
अंदरूनी कॉलबैक के उन सभी इंस्टेंस का इस्तेमाल करना सही है जो सही कॉलबैक
लागू करते हैं.
StateMachine
से दूसरे के बीच डेटा को डालना
अब तक, इस दस्तावेज़ में सिर्फ़ सबटास्क में काम को व्यवस्थित करने का तरीका बताया गया है, लेकिन सबटास्क को वैल्यू को वापस कॉलर को रिपोर्ट करना भी ज़रूरी है. सबटास्क तार्किक रूप से एसिंक्रोनस होते हैं, इसलिए उनके नतीजों को कॉलबैक का इस्तेमाल करके कॉलर को बताया जाता है. इस काम को करने के लिए, सबटास्क एक ऐसे सिंक इंटरफ़ेस की जानकारी देता है जिसे इसके कंस्ट्रक्टर के ज़रिए इंजेक्ट किया जाता है.
class BarProducer implements StateMachine {
// Callers of BarProducer implement the following interface to accept its
// results. Exactly one of the two methods will be called by the time
// BarProducer completes.
interface ResultSink {
void acceptBarValue(Bar value);
void acceptBarError(BarException exception);
}
private final ResultSink sink;
BarProducer(ResultSink sink) {
this.sink = sink;
}
… // StateMachine steps that end with this::complete.
private StateMachine complete(Tasks tasks) {
if (hasError()) {
sink.acceptBarError(getError());
return DONE;
}
sink.acceptBarValue(getValue());
return DONE;
}
}
इसके बाद, कॉलर StateMachine
ऐसा दिखेगा.
class Caller implements StateMachine, BarProducer.ResultSink {
interface ResultSink {
void acceptCallerValue(Bar value);
void acceptCallerError(BarException error);
}
private final ResultSink sink;
private Bar value;
Caller(ResultSink sink) {
this.sink = sink;
}
@Override
@Nullable
public StateMachine step(Tasks tasks) {
tasks.enqueue(new BarProducer((BarProducer.ResultSink) this));
return this::processResult;
}
@Override
public void acceptBarValue(Bar value) {
this.value = value;
}
@Override
public void acceptBarError(BarException error) {
sink.acceptCallerError(error);
}
private StateMachine processResult(Tasks tasks) {
// Since all enqueued subtasks resolve before `processResult` starts, one of
// the `BarResultSink` callbacks must have been called by this point.
if (value == null) {
return DONE; // There was a previously reported error.
}
var finalResult = computeResult(value);
sink.acceptCallerValue(finalResult);
return DONE;
}
}
पिछले उदाहरण में कुछ चीज़ों के बारे में बताया गया है. Caller
को अपने नतीजे फिर से दिखाने होंगे और अपने Caller.ResultSink
की जानकारी देनी होगी. Caller
, BarProducer.ResultSink
कॉलबैक लागू करता है. फिर से शुरू करने पर, processResult
यह जांच करता है कि
value
शून्य है या नहीं. इससे यह पता चलता है कि कोई गड़बड़ी हुई या नहीं. सबटास्क या स्काईवैल्यू लुकअप से आउटपुट स्वीकार करने के बाद, यह एक सामान्य व्यवहार पैटर्न है.
ध्यान दें कि acceptBarError
लागू करने से नतीजे Caller.ResultSink
को तेज़ी से फ़ॉरवर्ड हो जाते हैं, जैसा कि गड़बड़ी की जानकारी देने में ज़रूरी है.
टॉप-लेवल StateMachine
के विकल्पों के बारे में Driver
में जानकारी दी गई है.
इसके अलावा, इन्हें स्काई फ़ंक्शन में जोड़ें.
गड़बड़ी को संभालना
Tasks.lookUp
कॉलबैक में पहले से गड़बड़ी की स्थिति का उदाहरण मौजूद है. साथ ही, StateMachines
के बीच में वैल्यू
लागू करना के कुछ उदाहरण दिए गए हैं. अपवाद के अलावा, InterruptedException
के अलावा कोई और बदलाव नहीं किया जाता, बल्कि वैल्यू के तौर पर कॉलबैक के ज़रिए पास किया जाता है. ये कॉलबैक अक्सर खास या सिमैंटिक होते हैं, जिनमें से कोई एक वैल्यू या गड़बड़ी पास की जाती है.
अगले सेक्शन में, स्काईफ़्रेम की गड़बड़ी को हैंडल करने के बारे में ज़रूरी जानकारी दी गई है.
बबल करते समय गड़बड़ी हुई (--keepkeep)
गड़बड़ी की बबलिंग के दौरान, हो सकता है कि स्काईफ़ंक्शन दोबारा शुरू हो जाए. भले ही, अनुरोध किए गए सभी SkyValues उपलब्ध न हों. ऐसे मामलों में, Tasks
एपीआई समझौते की वजह से,
उस स्थिति को कभी ऐक्सेस नहीं किया जा सकेगा. हालांकि, StateMachine
को अब भी अपवाद
का प्रमोशन करना चाहिए.
अगली स्थिति तक पहुंचने पर ध्यान दिए बिना, प्रचार होना चाहिए, क्योंकि गड़बड़ी हैंडलिंग कॉलबैक को यह काम करना चाहिए. अंदरूनी StateMachine
के लिए, पैरंट पैरंट कॉलबैक का इस्तेमाल करके ऐसा किया जा सकता है.
टॉप लेवल StateMachine
के साथ यह काम किया जा सकता है, जो SkyFunction के साथ काम करता है. इसके लिए, setException
ValueOrExceptionProducer
वाले तरीके का इस्तेमाल किया जा सकता है.
इसके बाद, ValueOrExceptionProducer.tryProduceValue
अपवाद दिखाएगा,
भले ही, SkyValues मौजूद न हो.
अगर Driver
का सीधे तौर पर इस्तेमाल किया जा रहा है, तो SkyFunction से उसमें डाली गई गड़बड़ियों की जांच करना ज़रूरी है. भले ही, मशीन ने प्रोसेसिंग पूरी न की हो.
इवेंट हैंडलिंग
स्काई फ़ंक्शन के लिए, इवेंट को छोड़ने की ज़रूरत होने पर, StoredEventHandler
को SkyKeyComputeState में इंजेक्ट किया जाता है. इसके बाद, उन्हें StateMachine
में इंजेक्ट किया जाता है, जिन्हें उनकी ज़रूरत होती है. अब तक, कुछ फ़्रेम में स्काईफ़्रेम के छूटने की वजह से StoredEventHandler
की ज़रूरत होती थी, बशर्ते उन्हें फिर से न चलाया गया हो, लेकिन बाद में ठीक किया गया हो.
StoredEventHandler
इंजेक्शन को सुरक्षित रखा जाता है. ऐसा करने से, गड़बड़ियों को दूर करने के कॉलबैक से निकलने वाले इवेंट को आसानी से लागू किया जा सकता है.
Driver
Driver
, किसी बताए गए रूट StateMachine
से शुरू होने वाले StateMachine
के लागू होने की प्रक्रिया को मैनेज करने के लिए ज़िम्मेदार होता है. StateMachine
बार-बार होने वाले सबटास्क StateMachine
से हो सकता है, इसलिए सिर्फ़ Driver
एक से ज़्यादा सबटास्क मैनेज किए जा सकते हैं. इन सबटास्क में, स्ट्रक्चर्ड कॉनकरेंसी की मदद से एक ट्री स्ट्रक्चर तैयार किया जाता है. बेहतर परफ़ॉर्मेंस के लिए, Driver
सबटास्क में स्काईवैल्यू लुकअप को बैच में रखता है.
यहां दी गई एपीआई की मदद से, Driver
के आस-पास कई क्लास बनाई गई हैं.
public final class Driver {
public Driver(StateMachine root);
public boolean drive(SkyFunction.Environment env) throws InterruptedException;
}
Driver
, सिंगल रूट StateMachine
को पैरामीटर के रूप में लेता है. Driver.drive
कॉल करने पर, StateMachine
तब तक काम करता है, जब तक यह स्काईफ़्रेम को रीस्टार्ट किए बिना चल जाता है. StateMachine
के पूरा होने और गलत होने पर, यह 'सही' दिखाता है. ऐसा नहीं होने पर, यह पता चलता है कि सभी वैल्यू उपलब्ध नहीं हैं.
इससे Driver
की स्थिति StateMachine
पर एक साथ बनी रहती है. साथ ही, यह SkyKeyComputeState
में जोड़ने के लिए सही है.
Driver
को सीधे तौर पर इंस्टैंशिएट किया जा रहा है
आम तौर पर, StateMachine
तरीकों से अपने नतीजों को कॉलबैक के ज़रिए बताया जाता है. जैसा कि नीचे दिए गए उदाहरण में दिखाया गया है, Driver
को सीधे तौर पर इंस्टैंशिएट किया जा सकता है.
Driver
को SkyKeyComputeState
में लागू किया गया है. साथ ही, इसमें ResultSink
को थोड़ा और नीचे तय करने के लिए भी लागू किया गया है. टॉप लेवल पर, State
ऑब्जेक्ट, कंप्यूटेशन के नतीजे के लिए एक सही रिसीवर होता है, क्योंकि यह Driver
के पूरे होने की गारंटी देता है.
class State implements SkyKeyComputeState, ResultProducer.ResultSink {
// The `Driver` instance, containing the full tree of all `StateMachine`
// states. Responsible for calling `StateMachine.step` implementations when
// asynchronous values are available and performing batched SkyFrame lookups.
//
// Non-null while `result` is being computed.
private Driver resultProducer;
// Variable for storing the result of the `StateMachine`
//
// Will be non-null after the computation completes.
//
private ResultType result;
// Implements `ResultProducer.ResultSink`.
//
// `ResultProducer` propagates its final value through a callback that is
// implemented here.
@Override
public void acceptResult(ResultType result) {
this.result = result;
}
}
नीचे दिया गया कोड ResultProducer
को स्केच करता है.
class ResultProducer implements StateMachine {
interface ResultSink {
void acceptResult(ResultType value);
}
private final Parameters parameters;
private final ResultSink sink;
… // Other internal state.
ResultProducer(Parameters parameters, ResultSink sink) {
this.parameters = parameters;
this.sink = sink;
}
@Override
public StateMachine step(Tasks tasks) {
… // Implementation.
return this::complete;
}
private StateMachine complete(Tasks tasks) {
sink.acceptResult(getResult());
return DONE;
}
}
फिर लेज़ी तरीके से नतीजे की गिनती करने वाला कोड ऐसा दिख सकता है.
@Nullable
private Result computeResult(State state, Skyfunction.Environment env)
throws InterruptedException {
if (state.result != null) {
return state.result;
}
if (state.resultProducer == null) {
state.resultProducer = new Driver(new ResultProducer(
new Parameters(), (ResultProducer.ResultSink)state));
}
if (state.resultProducer.drive(env)) {
// Clears the `Driver` instance as it is no longer needed.
state.resultProducer = null;
}
return state.result;
}
Driver
एम्बेड किया जा रहा है
अगर StateMachine
कोई वैल्यू बनाता है और कोई अपवाद नहीं बढ़ाता है, तो एम्बेड करने की प्रक्रिया एक और तरीका हो सकता है
Driver
जो नीचे दिए गए उदाहरण में दिखाया गया है.
class ResultProducer implements StateMachine {
private final Parameters parameters;
private final Driver driver;
private ResultType result;
ResultProducer(Parameters parameters) {
this.parameters = parameters;
this.driver = new Driver(this);
}
@Nullable // Null when a Skyframe restart is needed.
public ResultType tryProduceValue( SkyFunction.Environment env)
throws InterruptedException {
if (!driver.drive(env)) {
return null;
}
return result;
}
@Override
public StateMachine step(Tasks tasks) {
… // Implementation.
}
SkyFunction में ऐसा कोड हो सकता है जो इस तरह दिखे (जहां State
खास तरह का SkyKeyComputeState
हो).
@Nullable // Null when a Skyframe restart is needed.
Result computeResult(SkyFunction.Environment env, State state)
throws InterruptedException {
if (state.result != null) {
return state.result;
}
if (state.resultProducer == null) {
state.resultProducer = new ResultProducer(new Parameters());
}
var result = state.resultProducer.tryProduceValue(env);
if (result == null) {
return null;
}
state.resultProducer = null;
return state.result = result;
}
Skyframe की सिंक्रोनस कोडिंग शैली के लिए, StateMachine
को लागू करना, Driver
को जोड़ना बेहतर होता है.
ऐसी राज्य मशीनी कंपनियां जो अपवाद पैदा कर सकती हैं
अगर ऐसा नहीं है, तो फिर SkyKeyComputeState
सिंक करने लायक ValueOrExceptionProducer
और ValueOrException2Producer
क्लास मौजूद होती हैं, जो सिंक्रोनस स्काई फ़ंक्शन कोड से मैच
करने के लिए सिंक्रोनस एपीआई उपलब्ध कराती हैं.
ValueOrExceptionProducer
ऐब्सट्रैक्ट क्लास में ये तरीके शामिल हैं.
public abstract class ValueOrExceptionProducer<V, E extends Exception>
implements StateMachine {
@Nullable
public final V tryProduceValue(Environment env)
throws InterruptedException, E {
… // Implementation.
}
protected final void setValue(V value) { … // Implementation. }
protected final void setException(E exception) { … // Implementation. }
}
इसमें, एक एंबेड किया गया Driver
इंस्टेंस होता है और यह एम्बेड किए गए ड्राइवर के ResultProducer
क्लास से मेल खाता है. साथ ही, यह SkyFunction के साथ इंटरफ़ेस को भी इसी तरह से जोड़ता है. ResultSink
लागू करने के बजाय,
लागू होने वाले setValue
या setException
को कॉल किया जाता है.
जब दोनों होते हैं, तो अपवाद को प्राथमिकता दी जाती है. tryProduceValue
तरीके का इस्तेमाल करके, एसिंक्रोनस कॉलबैक कोड को सिंक्रोनस कोड के साथ ब्रिज किया जाता है. साथ ही, इसे सेट करने पर अपवाद भी आता है.
जैसा कि पहले बताया गया है, गड़बड़ी की जानकारी देने के दौरान गड़बड़ी होने पर भी कोई गड़बड़ी हो सकती है. भले ही, मशीन पूरी तरह से न हुई हो, क्योंकि सभी इनपुट उपलब्ध नहीं हैं. इस बात को शामिल करने के लिए, tryProduceValue
मशीन चलाने के पहले, सेट किए गए सभी अपवाद खारिज कर देती है.
इपिलॉग: आखिर में कॉलबैक हटाता है
StateMachine
s बहुत असरदार होते हैं. हालांकि, एसिंक्रोनस तरीके से कैलकुलेशन करने के लिए, बॉयलरप्लेट से बेहतर तरीका लिया जाता है. Runnable
ListenableFuture
विश्लेषण ज़्यादातर सीपीयू से जुड़ा होता है और
डिस्क I/O के लिए कोई भी एसिंक्रोनस एपीआई नहीं है. आखिर में, कॉलबैक
को ऑप्टिमाइज़ करना अच्छा होता है, क्योंकि इनमें सीखने की क्षमता और पढ़ने की क्षमता पर असर पड़ता है.
Java वर्चुअल थ्रेड सबसे ज़्यादा फ़ायदेमंद विकल्पों में से एक है. कॉलबैक लिखने के बजाय, सब कुछ सिंक्रोनस, ब्लॉकिंग
कॉल के साथ बदल दिया जाता है. ऐसा इसलिए किया जा सकता है, क्योंकि वर्चुअल थ्रेड रिसॉर्स को जोड़ना, प्लैटफ़ॉर्म थ्रेड के जैसा नहीं माना जाता, क्योंकि यह सस्ता होता है. हालांकि, वर्चुअल थ्रेड में भी, थ्रेड सिंक करने और सिंक करने के प्रिमिटिव तरीका इस्तेमाल करके, सिंक करने में आसान बदलाव करना बहुत महंगा पड़ता है. हमने StateMachine
s से
Java वर्चुअल थ्रेड में डेटा माइग्रेट किया. इससे उनका धीरे-धीरे इस्तेमाल होने लगा.
इससे, पूरी तरह सुरक्षित और विश्लेषण करने में लगने वाले समय में करीब तीन गुना बढ़ोतरी हुई. हालांकि, वर्चुअल थ्रेड अब भी झलक की सुविधा के तौर पर उपलब्ध हैं. इसलिए, हो सकता है कि परफ़ॉर्मेंस बेहतर होने की तारीख पर इस माइग्रेशन की प्रोसेस बाद में की जाए.
विचार करने का एक और तरीका लूम कोरूटीन के उपलब्ध होने का इंतज़ार करता है. यहां एक फ़ायदा यह है कि कोऑपरेटिव मल्टीटास्किंग का इस्तेमाल करके सिंक करने की प्रक्रिया को कम किया जा सकता है.
अगर सब कुछ काम नहीं करता, तो लो-लेवल बाइट कोड फिर से लिखने की सुविधा भी एक कारगर विकल्प हो सकती है. ज़रूरत के मुताबिक ऑप्टिमाइज़ेशन की वजह से, हो सकता है कि परफ़ॉर्मेंस को हासिल करने के लिए, खुद करके लिखे गए कॉलबैक कोड का इस्तेमाल किया जाए.
Appendix
कॉलबैक हेल
कॉलबैक हेल, कॉलबैक का इस्तेमाल करने वाले एसिंक्रोनस कोड में एक बड़ी समस्या है. इसकी वजह यह है कि बाद के चरण में कंटिन्यूशन को पिछले चरण में नेस्ट किया गया है. अगर अंदर जाने से जुड़े कई तरीके हैं, तो इस नेस्टिंग का इस्तेमाल करना बहुत मुश्किल हो सकता है. कंट्रोल फ़्लो के साथ जोड़ने पर, कोड मैनेज नहीं हो पाता.
class CallbackHell implements StateMachine {
@Override
public StateMachine step(Tasks task) {
doA();
return (t, l) -> {
doB();
return (t1, l2) -> {
doC();
return DONE;
};
};
}
}
नेस्ट किए गए तरीकों को लागू करने का एक फ़ायदा यह भी है कि बाहरी चरण के स्टैक फ़्रेम को सुरक्षित रखा जा सकता है. Java में कैप्चर किए गए lambda वैरिएबल को असरदार माना जाना चाहिए. इसलिए, ऐसे वैरिएबल का इस्तेमाल करना मुश्किल हो सकता है. डीप नेस्टिंग को, lambdas के बजाय नीचे बताए गए तरीके से, वापस आने के तरीके के तौर पर इस्तेमाल करने से बचना चाहिए.
class CallbackHellAvoided implements StateMachine {
@Override
public StateMachine step(Tasks task) {
doA();
return this::step2;
}
private StateMachine step2(Tasks tasks) {
doB();
return this::step3;
}
private StateMachine step3(Tasks tasks) {
doC();
return DONE;
}
}
कॉलबैक तब भी हो सकता है, जब runAfter
इंजेक्शन
वाले पैटर्न का इस्तेमाल बहुत ज़्यादा किया गया हो. हालांकि, इंटरसेप्शन वाले इंटरसेप्शन से इंटरसेप्शन वाले इंजेक्शन से बचा जा सकता है.
उदाहरण: चेन्ड स्काईवैल्यू लुकअप
अक्सर ऐसा होता है कि ऐप्लिकेशन लॉजिक को स्काईवैल्यू लुकअप की निर्भर चेन की ज़रूरत होती है. उदाहरण के लिए, अगर कोई दूसरा स्काईकी पहले SkyValue पर निर्भर करता है. इस समझदारी से काम करने पर, यह एक जटिल और नेस्ट की गई कॉलबैक संरचना होगी.
private ValueType1 value1;
private ValueType2 value2;
private StateMachine step1(...) {
tasks.lookUp(key1, (Consumer<SkyValue>) this); // key1 has type KeyType1.
return this::step2;
}
@Override
public void accept(SkyValue value) {
this.value1 = (ValueType1) value;
}
private StateMachine step2(...) {
KeyType2 key2 = computeKey(value1);
tasks.lookup(key2, this::acceptValueType2);
return this::step3;
}
private void acceptValueType2(SkyValue value) {
this.value2 = (ValueType2) value;
}
हालांकि, जारी रखने के तरीके, रेफ़रंस के तौर पर बताए गए हैं, इसलिए राज्य के ट्रांज़िशन के दौरान कोड प्रोसेस करता है: step2
के बाद step1
. ध्यान दें, यहां value2
को असाइन करने के लिए, Lambda फ़ंक्शन का इस्तेमाल किया जाता है. इससे कोड का क्रम, ऊपर से नीचे की ओर कंप्यूटेशन के क्रम से मेल खाता है.
कई तरह की सलाह
पढ़ने की क्षमता: एक्ज़ीक्यूशन ऑर्डर करना
पढ़ने में आसान बनाने के लिए, कोड लागू किए जाने की प्रक्रिया में StateMachine.step
लागू करने और कॉलबैक लागू करने के तुरंत बाद कोड में पास किए जाने की कोशिश करें. हालांकि, यह हमेशा संभव नहीं होता है कि कहां पर कंट्रोल फ़्लो की शुरुआत हो. ऐसे मामलों में ज़्यादा टिप्पणियां मददगार हो सकती हैं.
उदाहरण: चेन्ड स्काईवैल्यू लुकअप को पूरा करने के लिए एक इंटरमीडिएट तरीके का रेफ़रंस दिया जाता है. इससे पढ़ने के लिए कुछ हद तक परफ़ॉर्मेंस होती है, जो कि शायद यहां सही है.
जनरेशन से जुड़ी हाइपोथीसिस
मीडियम साइज़ के ऑब्जेक्ट Java ऑब्जेक्ट, Java कचरा इकट्ठा करने वाले उपकरण के जनरेशन की परिकल्पना को तोड़ते हैं. इसे ऐसे ऑब्जेक्ट को हैंडल करने के लिए डिज़ाइन किया जाता है जो बहुत कम समय के लिए या हमेशा के लिए जीवित रहते हैं. परिभाषा के मुताबिक,
SkyKeyComputeState
में मौजूद ऑब्जेक्ट, इस अनुमान का उल्लंघन करते हैं. इस तरह के ऑब्जेक्ट में, अब भी चल रहे StateMachine
s के कंस्ट्रक्शन ट्री शामिल होते हैं. Driver
में रूट किए गए इन ऑब्जेक्ट में, बीच के समय तक चलने वाली वैल्यू को निलंबित किया जाता है, ताकि एसिंक्रोनस कंप्यूटेशन पूरा होने का इंतज़ार किया जा सके.
यह JDK19 में कम खराब लगता है, लेकिन StateMachine
s का इस्तेमाल करने पर, कभी-कभी GC के समय में बढ़ोतरी होती है. ऐसा तब भी होता है, जब रिसाइकल बिन में अचानक गिरावट आए. StateMachine
के इंटरमीडिएट लाइफ़ में बढ़ोतरी होती है. इसलिए, उन्हें पुरानी पीढ़ी के लिए प्रमोट किया जा सकता है. इस वजह से, वे तेज़ी से भर जाते हैं. इस वजह से, क्लीन अप करने के लिए ज़्यादा महंगे या बड़े जीसी की ज़रूरत पड़ती है.
शुरुआती सावधानी StateMachine
वैरिएबल के इस्तेमाल को कम करना है, लेकिन हमेशा ऐसा करना संभव नहीं है, उदाहरण के लिए, अगर एक से ज़्यादा राज्यों में मान की ज़रूरत है. जहां भी हो सके, लोकल स्टैक step
वैरिएबल युवा पीढ़ी के वैरिएबल होते हैं और बेहतर तरीके से GCs होते हैं.
StateMachine
वैरिएबल के लिए, चीज़ों को सबटास्क में बांटना और StateMachine
के बीच वैल्यू को अपने-आप लागू करना के लिए सुझाए गए पैटर्न को फ़ॉलो करना भी मददगार होता है. ध्यान रखें कि पैटर्न फ़ॉलो करते समय, सिर्फ़ पैरंट StateMachine
के रेफ़रंस वाले पैरंट डोमेन, StateMachine
से जुड़े होते हैं, न कि पैटर्न में. इसका मतलब है कि जब बच्चे नतीजों को कॉलबैक करने की सुविधा का इस्तेमाल करके माता-पिता को पूरा करने के साथ-साथ अपडेट करते हैं, तो वे अपने-आप दायरे से बाहर हो जाते हैं और जीसी की ज़रूरी शर्तें पूरी कर लेते हैं.
आखिर में, कुछ मामलों में StateMachine
वैरिएबल की ज़रूरत पिछली स्थितियों में होती है, लेकिन बाद की स्थितियों में नहीं. अगर किसी बड़े ऑब्जेक्ट के बारे में यह पता है कि अब उनकी ज़रूरत नहीं है, तो इस तरह के रेफ़रंस को शून्य करना फ़ायदेमंद हो सकता है.
नया नाम
किसी तरीके का नाम रखते समय, आम तौर पर इस तरीके में होने वाले व्यवहार को नाम दिया जा सकता है. इस बारे में कम जानकारी है कि StateMachine
में ऐसा कैसे किया जा सकता है, क्योंकि कोई स्टैक मौजूद नहीं है. उदाहरण के लिए, मान लें कि तरीका foo
एक सब-तरीका bar
कहता है. इसका StateMachine
में अनुवाद, राज्य के क्रम foo
में हो सकता है, जिसके बाद bar
हो सकता है. foo
में अब यह व्यवहार शामिल नहीं है
bar
. इस वजह से, राज्यों के लिए नाम देने के तरीके सीमित हो जाते हैं और
स्थानीय व्यवहार दिखाते हैं.
एक साथ कई खातों का ट्री डायग्राम
नीचे स्ट्रक्चर्ड कॉनकरेंसी में डायग्राम का वैकल्पिक व्यू दिया गया है जो ट्री स्ट्रक्चर को बेहतर ढंग से दिखाता है. ब्लॉक एक छोटा पेड़ बनाते हैं.
-
वैल्यू उपलब्ध न होने पर, स्काईफ़्रेम की शुरुआत से ही रीस्टार्ट करने की बात होती है.↩
-
ध्यान दें कि
step
कोInterruptedException
इस्तेमाल करने की अनुमति है, लेकिन इसके कुछ उदाहरण शामिल नहीं हैं. Bazen कोड में कुछ कम तरीके होते हैं, जो इस अपवाद को खारिज करते हैं औरDriver
पर लागू करते हैं. इसके बारे में बाद में बताया गया है, जोStateMachine
को चलाता है. ज़रूरत न होने पर यह एलान नहीं किया जाता कि वह फेंका गया है.↩ -
एक ही समय पर काम करने वाले सबटास्क
ConfiguredTargetFunction
के ज़रिए प्रेरित थे, जो हर डिपेंडेंसी के लिए स्वतंत्र काम करते हैं. सभी डिपेंडेंसी को एक साथ प्रोसेस करने वाले मुश्किल डेटा स्ट्रक्चर में हेर-फेर करने के बजाय, कमियां पेश करते हुए, हर डिपेंडेंसी का अपना अलगStateMachine
होता है.↩ -
एक बार में एक से ज़्यादा
tasks.lookUp
कॉल को एक साथ भेजा जाता है. एक साथ चलने वाले सबटास्क में होने वाले लुकअप की मदद से, अलग-अलग बैच बनाए जा सकते हैं.↩ -
सिद्धांत के तौर पर, यह Java की स्ट्रक्चर्ड कॉनकरेंसी की तरह है jeps/428.↩
-
ऐसा करना, किसी थ्रेड को पैदा करने और उसे क्रम में लगाने के लिए इस्तेमाल करने जैसा है.↩