टास्क ऐसे प्रोसेस होते हैं जो एक खास ऑपरेशन करने के लिए बने होते हैं। अधिकतर ये दूसरे प्रोसेस से बात नहीं करते, लेकिन जिस प्रोसेस ने टास्क शुरू किया था, उसे ये परिणाम लौटा सकते हैं।
काम को समानांतर करने के लिए टास्क का इस्तेमाल खूब होता है।
async/await
टास्क शुरू करने के लिए Task.async/1 का इस्तेमाल कीजिए। यह एक अनाम फंक्शन को आर्गुमेंट की तरह लेता है और उसे एक नए प्रोसेस में चलाता है, जो कॉल करने वाले प्रोसेस से जुड़ा होता है। यह एक %Task{} स्ट्रक्ट लौटाता है।
चलने का परिणाम पाने के लिए %Task{} स्ट्रक्ट को Task.await/2 में पास कीजिए। यह टास्क के पूरा होने की प्रतीक्षा करता है और उसका परिणाम लौटाता है। दूसरा आर्गुमेंट टाइमआउट है, जो मिलीसेकंड में होता है और जिसका डिफॉल्ट मान 5000 है। टास्क को बिना टाइमआउट के चलाने के लिए टाइमआउट की जगह :infinity वैल्यू पास की जा सकती है।
task = Task.async(fn -> Enum.sum(0..999_999) end)
# => %Task{
# owner: #PID<0.110.0>,
# pid: #PID<0.119.0>,
# ref: #Reference<0.3715954046.815529991.83334>
# }
Task.await(task)
# => 499999500000
ध्यान दीजिए कि टास्क शुरू करने और उसकी प्रतीक्षा करने के बीच, टास्क शुरू करने वाला प्रोसेस रुकता नहीं है और दूसरे काम भी कर सकता है।
Task.async/1 से शुरू किए गए हर टास्क की प्रतीक्षा करनी चाहिए, क्योंकि वह कॉल करने वाले प्रोसेस को एक संदेश भेजता है। हर टास्क के लिए Task.await/2 सिर्फ एक बार कॉल किया जा सकता है।
start/start_link
अगर आप टास्क सिर्फ साइड इफेक्ट के लिए शुरू करना चाहते हैं, तो Task.start/1 या Task.start_link/1 इस्तेमाल कीजिए। Task.start/1 ऐसा टास्क शुरू करता है जो कॉल करने वाले प्रोसेस से जुड़ा नहीं होता, और Task.start_link/1 ऐसा टास्क शुरू करता है जो कॉल करने वाले प्रोसेस से जुड़ा होता है। दोनों फंक्शन {:ok, pid} टपल लौटाते हैं।
अधिकतर ऐसा होता है कि एसिंक टास्क शुरू किए जाते हैं और फिर Enum.map/2 की मदद से उनकी प्रतीक्षा की जाती है, इस तरह:
inputs
|> Enum.map(&Task.async(fn -> function.(&1) end))
|> Enum.map(&Task.await(&1, 1_000))
यह जानना ज़रूरी है कि Task.await सिंक्रोनस है, और जब भी इसे कॉल किया जाता है, यह अपनी टाइमआउट घड़ी 0 से शुरू करता है। इसका मतलब है कि जब इसे इस तरह एक के बाद एक इस्तेमाल किया जाता है, तो टाइमआउट जुड़ते चले जाते हैं।
जिस पहले टास्क की प्रतीक्षा की जाती है, वह टाइमआउट से ज़्यादा देर नहीं चलता। लेकिन जिस दूसरे टास्क की प्रतीक्षा की जाती है, वह पहला टास्क जितनी देर चला, उससे टाइमआउट जोड़कर उतनी देर तक चल सकता है।
ऐसी स्थिति की कल्पना कीजिए जहाँ हर टास्क पिछले टास्क से थोड़ा ज़्यादा समय लेता है, लेकिन दो टास्क के चलने के समय का अंतर प्रतीक्षा के टाइमआउट से कम है।
inputs = 1..10
function = fn n -> :timer.sleep(n * 900) && n end
उन टास्क को चलने में कितना समय लगेगा, यह माइक्रोसेकंड में नापने के लिए हम Erlang का एक फंक्शन, :timer.tc/1, इस्तेमाल कर सकते हैं।
:timer.tc(fn ->
inputs
|> Enum.map(&Task.async(fn -> function.(&1) end))
|> Enum.map(&Task.await(&1, 1_000))
end)
# => {9004517, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]}
9004517 माइक्रोसेकंड यानी 9 सेकंड, जो दिए गए 1 सेकंड (1000 मिलीसेकंड) के टाइमआउट से कहीं ज़्यादा है।
अगर आपको यही व्यवहार चाहिए, तो बहुत बढ़िया, ऐसे ही कीजिए! अगर नहीं, तो दूसरा तरीका यह है कि आप Task.async_stream/3 या Task.await_many/2 में से कोई एक इस्तेमाल कीजिए।
Task.async_stream/3Task.async_stream/3 आपको एक ही फंक्शन कई अलग-अलग इनपुट के लिए चलाने देता है, और हर इनपुट अपने अलग टास्क में चलता है। टास्क मौजूदा प्रोसेस से जुड़े होते हैं, ठीक वैसे ही जैसे Task.async/1 से चलाने पर होते हैं। यह एक स्ट्रीम लौटाता है।
यह एक टाइमआउट लेता है जो हर टास्क पर अलग-अलग लागू होता है। अब वही इनपुट और वही फंक्शन Task.async_stream/3 के साथ इस्तेमाल करके देखिए:
inputs
|> Task.async_stream(function, timeout: 1_000)
|> Enum.to_list()
# => ** (exit) exited in: Task.Supervised.stream(1000)
# ** (EXIT) time out
# (elixir 1.11.3) lib/task/supervised.ex:304: Task.Supervised.stream_reduce/7
# (elixir 1.11.3) lib/enum.ex:3473: Enum.reverse/1
# (elixir 1.11.3) lib/enum.ex:3066: Enum.to_list/1
अगर आपको एक ही फंक्शन अलग-अलग इनपुट के साथ चलाना है, तो Task.async_stream/3 बहुत काम का है। यह कई और काम के विकल्प लेता है, जिनसे आपको बहुत नियंत्रण मिलता है, जैसे एक साथ कितने टास्क चलाने हैं और टास्क का टाइमआउट होने पर क्या करना है।
अगर आपको ऐसे टास्क चलाने हैं जो अलग-अलग काम करते हैं, तो आपको शायद इसके बजाय Task.await_many/2 की ज़रूरत पड़े।
Task.await_many/2Task.await_many/2 को Elixir 1.11 में जोड़ा गया था। यह आपको कई टास्क की एक साझा टाइमआउट के साथ प्रतीक्षा करने देता है। अब वही इनपुट और वही फंक्शन Task.await_many/2 के साथ इस्तेमाल करके देखिए:
inputs
|> Enum.map(&Task.async(fn -> function.(&1) end))
|> Task.await_many(1_000)
# => ** (exit) exited in: Task.await_many([...], 1000)
# ** (EXIT) time out
# (elixir 1.11.3) lib/task.ex:725: Task.await_many/5
# (elixir 1.11.3) lib/task.ex:709: Task.await_many/2