টাস্ক হলো এমন প্রসেস যা একটি নির্দিষ্ট অপারেশন সম্পাদন করার উদ্দেশ্যে তৈরি। এরা সাধারণত অন্য প্রসেসের সাথে যোগাযোগ করে না, তবে টাস্কটি যে প্রসেস শুরু করেছিল তাকে একটি ফলাফল রিটার্ন করতে পারে।
কাজ প্যারালালাইজ করতে সাধারণত টাস্ক ব্যবহার করা হয়।
async/await
একটি টাস্ক শুরু করতে Task.async/1 ব্যবহার করুন। এটি আর্গুমেন্ট হিসেবে একটি অ্যানোনিমাস ফাংশন নেয় এবং কলার প্রসেসের সাথে লিংকড একটি নতুন প্রসেসে সেটি সম্পাদন করে। এটি একটি %Task{} স্ট্রাক্ট রিটার্ন করে।
সম্পাদনের ফলাফল পেতে Task.await/2 ফাংশনে %Task{} স্ট্রাক্টটি পাস করুন। এটি টাস্কটি শেষ হওয়া পর্যন্ত অপেক্ষা করবে এবং তার ফলাফল রিটার্ন করবে। দ্বিতীয় আর্গুমেন্টটি মিলিসেকেন্ডে একটি টাইমআউট, যার ডিফল্ট মান ৫০০০। টাইমআউট ছাড়াই টাস্কটি সম্পাদনের সুযোগ দিতে টাইমআউট হিসেবে :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
লক্ষ্য করুন, টাস্ক শুরু করা এবং টাস্কটি await করার মধ্যে যে প্রসেসটি টাস্ক শুরু করেছিল সেটি ব্লক হয় না এবং অন্য কিছু কাজও করতে পারে।
Task.async/1 দিয়ে শুরু করা যেকোনো টাস্কের জন্য await করা উচিত, কারণ এটি কলিং প্রসেসে একটি মেসেজ পাঠাবে। প্রতিটি টাস্কের জন্য Task.await/2 কেবল একবারই কল করা যায়।
start/start_link
কেবল সাইড-এফেক্টের জন্য একটি টাস্ক শুরু করতে চাইলে Task.start/1 বা Task.start_link/1 ব্যবহার করুন। Task.start/1 এমন একটি টাস্ক শুরু করে যা কলিং প্রসেসের সাথে লিংকড নয়, আর Task.start_link/1 এমন একটি টাস্ক শুরু করে যা কলিং প্রসেসের সাথে লিংকড। দুটি ফাংশনই একটি {:ok, pid} টাপল রিটার্ন করে।
async টাস্ক শুরু করে তারপর Enum.map/2 ব্যবহার করে সেগুলোর জন্য await করা একটি কমন প্যাটার্ন, এভাবে:
inputs
|> Enum.map(&Task.async(fn -> function.(&1) end))
|> Enum.map(&Task.await(&1, 1_000))
এটি জানা জরুরি যে Task.await সিনক্রোনাস, এবং প্রতিবার এটি কল করা হলে এর টাইমআউট ঘড়ি ০ থেকে শুরু হয়। তার মানে এইভাবে ক্রমানুসারে ব্যবহার করলে টাইমআউটগুলো যোগ হতে থাকে।
প্রথম যে টাস্কটির জন্য await করা হয় সেটি টাইমআউটের চেয়ে বেশি সময় চলে না, কিন্তু দ্বিতীয় যে টাস্কটির জন্য await করা হয় সেটি প্রথম টাস্কটি যত সময় চলেছিল তার সাথে টাইমআউট যোগ করে তত সময় চলে।
এমন একটি পরিস্থিতি ভাবুন যেখানে প্রতিটি টাস্ক আগেরটির চেয়ে একটু বেশি সময় নেয়, কিন্তু টাস্ক চলার সময়ের পার্থক্য await টাইমআউটের চেয়ে কম।
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]}
৯০০৪৫১৭ মাইক্রোসেকেন্ড মানে ৯ সেকেন্ড, যা প্রদত্ত ১ সেকেন্ড (১০০০ মিলিসেকেন্ড) টাইমআউটের চেয়ে অনেক বেশি।
এটি যদি আপনার কাঙ্ক্ষিত আচরণ হয়, তবে দারুণ, এভাবেই এগিয়ে যান! না হলে বিকল্প হলো 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 এলিক্সির 1.11-এ চালু হয়েছিল। এটি একটি অভিন্ন টাইমআউট দিয়ে অনেক টাস্কের জন্য await করার সুযোগ দেয়। একই ইনপুট ও একই ফাংশন 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