2016-12-13 7 views
7

पर कॉल करता है, मुझे पता है कि जेनसेवर प्रक्रिया स्वयं को कॉल करना बहुत असंभव है क्योंकि आप अनिवार्य रूप से डेडलॉक हिट करते हैं। लेकिन, अगर मैं इस तरह की चीज करने का पसंदीदा तरीका हूं तो मैं उत्सुक हूं।जेनसेवर की संरचना करने का उचित तरीका स्वयं को

निम्नलिखित परिदृश्य मानें: मुझे एक कतार मिली है कि मैं चीजों को पॉप कर रहा हूं। यदि कतार खाली है, तो मैं इसे फिर से भरना चाहता हूं। मैं यह इसलिए की तरह की संरचना कर सकते हैं:

def handle_call(:refill_queue, state) do 
    new_state = put_some_stuff_in_queue(state) 
    {:reply, new_state} 
end 

def handle_call(:pop, state) do 
    if is_empty_queue(state) do 
    GenServer.call(self, :refill_queue) 
    end 

    val,new_state = pop_something(state) 

    {:reply, val, new_state} 
end 

बड़ी समस्या यहाँ है कि यह जब हम कतार फिर से भरना करने की कोशिश गतिरोध होगा। एक समाधान है कि मैं पहले उपयोग किए गए cast अधिक तो यह गतिरोध नहीं है का उपयोग करें। जैसा तो (रीफिल के लिए cast को call बदल)

def handle_cast(:refill_queue, state) do 

लेकिन इस मामले में, मुझे लगता है, यह काम नहीं करेगा async डाली कतार फिर से भरना करने के बाद से वास्तव में कतार अर्थ भरने से पहले pop मामले में वापस कर सकती है मैं एक खाली कतार को बंद करने की कोशिश करूंगा।

वैसे भी, मूल प्रश्न यह है: इसे संभालने का सबसे अच्छा तरीका क्या है? मुझे लगता है इस सवाल का जवाब सिर्फ pop कॉल के अंदर सीधे put_some_stuff_in_queue कॉल करने के लिए है, लेकिन मैं जाँच करने के लिए चाहता था। दूसरे शब्दों में, ऐसा लगता है ऐसा करना सही handle_call और handle_cast के रूप में संभव के रूप में सरल बनाया जाए और मूल रूप से सिर्फ अन्य कार्यों जहां असली काम होता है रैपर। जैसा कि आप सभी संभव मामलों आप बल्कि बारी कॉल handle_call(:bar) में handle_call(:foo) की तुलना के साथ सौदा होगा, को कवर करने की जरूरत है फिर, के रूप में कई handle_* कार्यों पैदा करते हैं।

+2

मैं एक सादा फ़ंक्शन 'refill_queue' कर दूंगा और इसे' handle_call (: pop) 'से कॉल करूँगा, यदि आपको सिंक्रोनस होने की आवश्यकता है। अन्यथा आपके पास इसे एसिंक को संभालने के लिए कई विकल्प हैं (एक और संदेश 'स्वयं' को भेजें, एक और प्रक्रिया हैंडलिंग रिफिलिंग आदि) –

+3

आपको ['GenStage'] (http://elixir-lang.org/blog पर एक नज़र डालना चाहिए)/2016/07/14/घोषणा-जननांग /), ऐसा लगता है जैसे यह उस कार्यक्षमता को प्रदान करता है जिसे आप ढूंढ रहे हैं। – mudasobwa

उत्तर

5

मॉड्यूल में reply/2 नामक एक फ़ंक्शन है। handle_call/3 कॉलबैक का दूसरा तर्क क्लाइंट से कनेक्शन है। आप कनेक्शन को संभालने के लिए एक नई प्रक्रिया बना सकते हैं और कॉलबैक क्लॉज में {:noreply, state} वापस कर सकते हैं। अपने उदाहरण का उपयोग करना:

defmodule Q do 
    use GenServer 

    ############ 
    # Public API 

    def start_link do 
    GenServer.start_link(__MODULE__, []) 
    end 

    def push(pid, x) do 
    GenServer.call(pid, {:push, x}) 
    end 

    def pop(pid) do 
    GenServer.call(pid, :pop) 
    end 

    ######## 
    # Helper 

    # Creates a new process and does a request to 
    # itself with the message `:refill`. Replies 
    # to the client using `from`. 
    defp refill(from) do 
    pid = self() 
    spawn_link fn -> 
     result = GenServer.call(pid, :refill) 
     GenServer.reply(from, result) 
    end 
    end 

    ########## 
    # Callback 

    def handle_call(:refill, _from, []) do 
    {:reply, 1, [2, 3]} 
    end 
    def handle_call(:refill, _from, [x | xs]) do 
    {:reply, x, xs} 
    end 
    def handle_call({:push, x}, _from, xs) when is_list(xs) do 
    {:reply, :ok, [x | xs]} 
    end 
    def handle_call(:pop, from, []) do 
    # Handles refill and the reply to from. 
    refill(from) 
    # Returns nothing to the client, but unblocks the 
    # server to get more requests. 
    {:noreply, []} 
    end 
    def handle_call(:pop, _from, [x | xs]) do 
    {:reply, x, xs} 
    end 
end 

और आप निम्नलिखित मिलेगा:

iex(1)> {:ok, pid} = Q.start_link() 
{:ok, #PID<0.193.0>} 
iex(2)> Q.pop(pid) 
1 
iex(3)> Q.pop(pid) 
2 
iex(4)> Q.pop(pid) 
3 
iex(5)> Q.pop(pid) 
1 
iex(6)> Q.pop(pid) 
2 
iex(7)> Q.pop(pid) 
3 
iex(8)> Q.push(pid, 4) 
:ok 
iex(9)> Q.pop(pid)  
4 
iex(10)> Q.pop(pid) 
1 
iex(11)> Q.pop(pid) 
2 
iex(12)> Q.pop(pid) 
3 
iex(13)> tasks = for i <- 1..10 do 
...(13)> Task.async(fn -> {"Process #{inspect i}", Q.pop(pid)} end) 
...(13)> end 
(...) 
iex(14)> for task <- tasks, do: Task.await(task) 
[{"Process 1", 1}, {"Process 2", 2}, {"Process 3", 1}, {"Process 4", 2}, 
{"Process 5", 3}, {"Process 6", 3}, {"Process 7", 2}, {"Process 8", 1}, 
{"Process 9", 1}, {"Process 10", 3}] 

तो यह संभव वास्तव में एक GenServer ही करने के लिए अनुरोध करने के लिए। आपको बस यह जानने की जरूरत है कि कैसे।

मुझे उम्मीद है कि इससे मदद मिलती है।

+0

हाँ, मुझे लगता है कि यह वही है जो मैं सोच रहा था। मैंने सोचा है कि 'से' तर्क का क्या उपयोग किया गया था, और अब यह और अधिक समझ में आता है। – Micah

+3

मुझे नहीं पता था कि आप बाद में जवाब दे सकते हैं, जबकि आप अन्य संदेशों को संसाधित करते हैं जो जानना बहुत ही बढ़िया है। परंतु! क्या यह एक ही समय में रीफिल को कॉल करने के लिए एकाधिक पॉप कॉल के लिए संभव नहीं होगा? मेरा मतलब है, अगर रीफिल 10 एमएमएस लेता है और हमारे पास प्रत्येक 3 एमएस पॉप पॉप होता है तो हमें 2 पॉप मिलेंगे (प्रत्येक एक और फिर से भरने वाला होगा) जबकि पहला रीफिल कर रहा है। क्योंकि वह रीफिल संदेश कतार में जाएगा, पहले से ही संदेश कतार में पॉप के बाद संसाधित होने के लिए, सही? –

+2

@IsmaelAbreu हाँ, आप सही हैं। आपको मेरे कोड में एक बग मिला। यदि आपके पास संदेश कतार में दो पॉप हैं तो यह दो रिफिल ट्रिगर करेगा। पहला रीफिल स्टैक को फिर से भर देगा। दूसरा रीफिल मान्य क्लॉज नहीं मिलेगा क्योंकि स्टैक खाली नहीं है, इसलिए सर्वर 'FunctionClauseError' के साथ क्रैश हो जाएगा। मैंने बग तय किया और नया खंड जोड़ा जब रिफिल को एक खाली खाली स्टैक मिल गया। धन्यवाद: डी –

2

आपको GenServer.call बनाने की आवश्यकता क्यों है?

def handle_call(:pop, state) do 
    new_state0 = if is_empty_queue(state) do 
    put_some_stuff_in_queue(state) 
    else 
    state 
    end 
    {val,new_state} = pop_something(new_state0) 

    {:reply, val, new_state} 
end 

या

def handle_call(:pop, state) do 
    {val, new_state} = state 
        |> is_empty_queue 
        |> case do 
          true -> 
          put_some_stuff_in_queue(state) 
          false -> 
          state 
         end 
        |> pop_something 

    {:reply, val, new_state} 
end 

तो कॉल करने नहीं-नहीं, लेकिन अन्य कार्यों बुला पूरी तरह संभव है।

+0

बुनियादी अंतर यह है कि अपने मूल कार्यान्वयन के साथ, मैं अलग से कतार भर कर सकते हैं अगर मैं की जरूरत है। ऐसे कई बार हो सकते हैं जब मैं इसे स्वतंत्र रूप से कुछ बंद करने के लिए करना चाहता हूं।बेशक, मैं अकेले refill_queue 'handle_call' को छोड़ सकता हूं, जो कि मैं अपेक्षा करता हूं, लेकिन मैं सोच रहा हूं कि इस तरह की स्थिति को संभालने का एक मूर्खतापूर्ण इलीक्सिर तरीका है या नहीं। – Micah

संबंधित मुद्दे