स्काईफ़्रेम स्टेट मशीन के लिए गाइड

समस्या की शिकायत करें सोर्स देखें

खास जानकारी

स्काईफ़्रेम 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 लुकअप

StateMachineSkyValues का पता लगाने के लिए Tasks.lookUp ओवरलोड का इस्तेमाल करता है. ये SkyFunction.Environment.getValueऔर SkyFunction.Environment.getValueOrThrow की भाषा से मेल खाते हैं. साथ ही, इनमें अपवाद के तौर पर एक जैसे सिंटैक्स होते हैं. लागू करने से तुरंत लुकअप नहीं होता है, बल्कि यह करने से पहले4 ज़्यादा से ज़्यादा लुकअप करता है. हो सकता है कि वैल्यू तुरंत उपलब्ध न हो, जैसे कि स्काईफ़्रेम को रीस्टार्ट करना ज़रूरी हो, ताकि कॉलर कॉलबैक से मिलने वाली नतीजे के साथ क्या करे.

StateMachine प्रोसेसर (Drivers और 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 और StateMachines के बनाए रखने तक, जारी रखना है या नहीं. इस सेक्शन में, कुछ तरीकों को हासिल किया गया है.

टर्मिनल सिक्वेंस एलिमेंट के तौर पर 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किसी और को काम के लिए इस्तेमाल करने का तर्क, चरण को आगे बढ़ाने के बजाय, ग़ैर-ज़रूरी या काम की बन जाता है. उदाहरण के लिए, अगर डेलिगेट का पहला stepSkyKey लुकअप का इस्तेमाल करता है जो डेलिगेट करने की स्थिति के लुकअप के बराबर हो सकता है, तो फ़ेज़ ऐडवांस में क्रम में चलने वाले लुकअप बन जाते हैं. जैसा कि नीचे दिए गए उदाहरण में दिखाया गया है, उन लोगों को सीधे तौर पर अपने ईमेल खाते का ऐक्सेस देना ज़्यादा सही होगा.

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 मशीन चलाने के पहले, सेट किए गए सभी अपवाद खारिज कर देती है.

इपिलॉग: आखिर में कॉलबैक हटाता है

StateMachines बहुत असरदार होते हैं. हालांकि, एसिंक्रोनस तरीके से कैलकुलेशन करने के लिए, बॉयलरप्लेट से बेहतर तरीका लिया जाता है. RunnableListenableFuture विश्लेषण ज़्यादातर सीपीयू से जुड़ा होता है और डिस्क I/O के लिए कोई भी एसिंक्रोनस एपीआई नहीं है. आखिर में, कॉलबैक को ऑप्टिमाइज़ करना अच्छा होता है, क्योंकि इनमें सीखने की क्षमता और पढ़ने की क्षमता पर असर पड़ता है.

Java वर्चुअल थ्रेड सबसे ज़्यादा फ़ायदेमंद विकल्पों में से एक है. कॉलबैक लिखने के बजाय, सब कुछ सिंक्रोनस, ब्लॉकिंग कॉल के साथ बदल दिया जाता है. ऐसा इसलिए किया जा सकता है, क्योंकि वर्चुअल थ्रेड रिसॉर्स को जोड़ना, प्लैटफ़ॉर्म थ्रेड के जैसा नहीं माना जाता, क्योंकि यह सस्ता होता है. हालांकि, वर्चुअल थ्रेड में भी, थ्रेड सिंक करने और सिंक करने के प्रिमिटिव तरीका इस्तेमाल करके, सिंक करने में आसान बदलाव करना बहुत महंगा पड़ता है. हमने StateMachines से 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 में मौजूद ऑब्जेक्ट, इस अनुमान का उल्लंघन करते हैं. इस तरह के ऑब्जेक्ट में, अब भी चल रहे StateMachines के कंस्ट्रक्शन ट्री शामिल होते हैं. Driver में रूट किए गए इन ऑब्जेक्ट में, बीच के समय तक चलने वाली वैल्यू को निलंबित किया जाता है, ताकि एसिंक्रोनस कंप्यूटेशन पूरा होने का इंतज़ार किया जा सके.

यह JDK19 में कम खराब लगता है, लेकिन StateMachines का इस्तेमाल करने पर, कभी-कभी GC के समय में बढ़ोतरी होती है. ऐसा तब भी होता है, जब रिसाइकल बिन में अचानक गिरावट आए. StateMachine के इंटरमीडिएट लाइफ़ में बढ़ोतरी होती है. इसलिए, उन्हें पुरानी पीढ़ी के लिए प्रमोट किया जा सकता है. इस वजह से, वे तेज़ी से भर जाते हैं. इस वजह से, क्लीन अप करने के लिए ज़्यादा महंगे या बड़े जीसी की ज़रूरत पड़ती है.

शुरुआती सावधानी StateMachine वैरिएबल के इस्तेमाल को कम करना है, लेकिन हमेशा ऐसा करना संभव नहीं है, उदाहरण के लिए, अगर एक से ज़्यादा राज्यों में मान की ज़रूरत है. जहां भी हो सके, लोकल स्टैक step वैरिएबल युवा पीढ़ी के वैरिएबल होते हैं और बेहतर तरीके से GCs होते हैं.

StateMachine वैरिएबल के लिए, चीज़ों को सबटास्क में बांटना और StateMachine के बीच वैल्यू को अपने-आप लागू करना के लिए सुझाए गए पैटर्न को फ़ॉलो करना भी मददगार होता है. ध्यान रखें कि पैटर्न फ़ॉलो करते समय, सिर्फ़ पैरंट StateMachineके रेफ़रंस वाले पैरंट डोमेन, StateMachineसे जुड़े होते हैं, न कि पैटर्न में. इसका मतलब है कि जब बच्चे नतीजों को कॉलबैक करने की सुविधा का इस्तेमाल करके माता-पिता को पूरा करने के साथ-साथ अपडेट करते हैं, तो वे अपने-आप दायरे से बाहर हो जाते हैं और जीसी की ज़रूरी शर्तें पूरी कर लेते हैं.

आखिर में, कुछ मामलों में StateMachine वैरिएबल की ज़रूरत पिछली स्थितियों में होती है, लेकिन बाद की स्थितियों में नहीं. अगर किसी बड़े ऑब्जेक्ट के बारे में यह पता है कि अब उनकी ज़रूरत नहीं है, तो इस तरह के रेफ़रंस को शून्य करना फ़ायदेमंद हो सकता है.

नया नाम

किसी तरीके का नाम रखते समय, आम तौर पर इस तरीके में होने वाले व्यवहार को नाम दिया जा सकता है. इस बारे में कम जानकारी है कि StateMachine में ऐसा कैसे किया जा सकता है, क्योंकि कोई स्टैक मौजूद नहीं है. उदाहरण के लिए, मान लें कि तरीका foo एक सब-तरीका bar कहता है. इसका StateMachine में अनुवाद, राज्य के क्रम foo में हो सकता है, जिसके बाद bar हो सकता है. foo में अब यह व्यवहार शामिल नहीं है bar. इस वजह से, राज्यों के लिए नाम देने के तरीके सीमित हो जाते हैं और स्थानीय व्यवहार दिखाते हैं.

एक साथ कई खातों का ट्री डायग्राम

नीचे स्ट्रक्चर्ड कॉनकरेंसी में डायग्राम का वैकल्पिक व्यू दिया गया है जो ट्री स्ट्रक्चर को बेहतर ढंग से दिखाता है. ब्लॉक एक छोटा पेड़ बनाते हैं.

स्ट्रक्चर्ड कॉनकरेंसी 3D


  1. वैल्यू उपलब्ध न होने पर, स्काईफ़्रेम की शुरुआत से ही रीस्टार्ट करने की बात होती है.

  2. ध्यान दें कि step को InterruptedException इस्तेमाल करने की अनुमति है, लेकिन इसके कुछ उदाहरण शामिल नहीं हैं. Bazen कोड में कुछ कम तरीके होते हैं, जो इस अपवाद को खारिज करते हैं और Driver पर लागू करते हैं. इसके बारे में बाद में बताया गया है, जो StateMachine को चलाता है. ज़रूरत न होने पर यह एलान नहीं किया जाता कि वह फेंका गया है.

  3. एक ही समय पर काम करने वाले सबटास्क ConfiguredTargetFunction के ज़रिए प्रेरित थे, जो हर डिपेंडेंसी के लिए स्वतंत्र काम करते हैं. सभी डिपेंडेंसी को एक साथ प्रोसेस करने वाले मुश्किल डेटा स्ट्रक्चर में हेर-फेर करने के बजाय, कमियां पेश करते हुए, हर डिपेंडेंसी का अपना अलग StateMachine होता है.

  4. एक बार में एक से ज़्यादा tasks.lookUp कॉल को एक साथ भेजा जाता है. एक साथ चलने वाले सबटास्क में होने वाले लुकअप की मदद से, अलग-अलग बैच बनाए जा सकते हैं.

  5. सिद्धांत के तौर पर, यह Java की स्ट्रक्चर्ड कॉनकरेंसी की तरह है jeps/428.

  6. ऐसा करना, किसी थ्रेड को पैदा करने और उसे क्रम में लगाने के लिए इस्तेमाल करने जैसा है.