Task - Java Future[T], Haskell Applicative

         ------+------------------------
               |    sync         async
         ------+------------------------
          one  |     T          Task(T)
               | 
          many |   Enum(T)     Stream(T)
         ------+------------------------

Task is the processes meant to execute one particular action throughout its life-cycle

to convert sequential code into concurrent code by computing a value asynchronously:

    iex> t = Task.async(fn -> 2 + 3 end)
     %Task{owner: #PID<0.64.0>, pid: #PID<0.656.0>, ref: #Reference<0.0.1.3558>}
    iex> r = 5 + 7
     12
    iex> z = r  + Task.await(t)
     17

task spawned with 'async' can be waited on by the caller process (and ONLY). it sends a message to the caller once the given computation is performed

the reply sent by the task will be in the format {ref, msg} where 'ref' is the monitoring reference held by the task

caller process asks for result by Task.await(pid) after that task process normally terminated
if caller is terminated before 'await', the task will also terminate

imagine you have this:

        x = heavy_fn()
        y = some_fn()
        x + y
you want to make the heavy_fn() async:
        x = Task.async(&heavy_fn/0)
        y = some_fn()
        Task.await(x) + y
if heavy_fn/0 fails, the whole computation will fail, including the parent process. in other words, an asynchronous task should be considered an extension of a process

if you don’t want to link the caller to the task, then you must use a supervised task with Task.Supervisor and call Task.Supervisor.async_nolink/2

avoid any of the following:

API

async(fun) -> __struct__

     iex> t = Task.async(fn -> 5 + 11 end)
       %Task{owner: #PID<0.58.0>, pid: #PID<0.71.0>, ref: #Reference<0.0.2.172>}
     iex> 12 + Task.await(t)
      28

   spawns a process that is linked to and monitored by the caller process 
   a Task struct is returned containing the relevant information

        
async(module, function, [arg]) -> _struct_

   starts a task that MUST be awaited on
   'function'  - this must be atom (function name in the module)

          defmodule Mymod
            do
              def myfunc x , y
              do
                x + y
              end
          end

      iex> t = Task.async(Mymod, :myfunc, [4 , 5])
       %Task{owner: #PID<0.58.0>, pid: #PID<0.67.0>, ref: #Reference<0.0.2.162>}
      iex> 12 + Task.await(t)
       21

              
await(task, timeout \\\\ 5000) -> term | no_return

   in case the task process dies, this function will exit with the same reason as the task
   if the timeout is exceeded, await will exit, however, the task will continue to run
   when the calling process exits, its exit signal will terminate the task 

if you are not expecting a reply, consider using Task.start_link/1 

if the caller crashes, the task will crash too and vice-versa
if this is not desired, consider using Task.start_link/1 

              
yield(task, timeout \\\\ 5000) ->  {:ok, result}  |  {:exit, reason}  |  nil

  returns nil if no reply arrived (in case of the timeout), and the monitor will remain active
  therefore yield/2 can be called multiple times on the same task
  in case the task process dies, this function will exit with the same reason as the task

  yield/2 is an alternative to await/2 where the caller will temporarily block,
  waiting until the task replies or crashes
  if the result does not arrive within the timeout it can be called again at a later moment
  if a reply does not arrive within the desired time, shutdown/2 can be used to stop the task

              
yield_many([task], timeout \\\\ 5000) -> [ {task , {:ok, term} | {:exit, term} | nil} ]

  this function receives a list of tasks
  and await for their replies at once in the given time interval
  similar to yield/2, if the task replied in the given interval,
  it will return {:ok, term}, {:exit, reason} if it crashed or
  'nil' if it timed out

              
start(fun) -> {:ok, pid}
start(module, function, [arg]) -> {:ok, pid}

   only used when the task is used for side-effects 

start_link(fun) ->  {:ok, pid}
start_link(mod, fun, args) ->  {:ok, pid}

   a task as part of a supervision tree

shutdown(task, timeout | :brutal_kill \\\\ 5000) ->  {:ok, term} | {:exit, term} | nil

  returns
  {:ok, reply} if the reply is received while shutting down the task
  {:exit, reason} if the task exited abornormally, otherwise nil

  if the caller is exiting with a reason other than :normal (and the task is not trapping exits)
  the caller’s exit signal will stop the task

this function assumes
    the task’s monitor is still active or the monitor’s :DOWN message is in the message queue
if it has been demonitored, or the message already received
    this function will block forever awaiting the message

              
find([task], msg) -> {term, t} | nil | no_return

  finds a task that matches the given message

  it exits if the task has failed
  useful in situations where multiple tasks are spawned and their results are collected later on
  for example, a GenServer can
            spawn tasks,
            store the tasks in a list and later
            use Task.find/2 to see if incoming messages are from any of the tasks

supervised tasks

to spawn a task inside a supervision tree with start_link/1/3:

Task.start_link(fn -> IO.puts "ok" end)

since these tasks are supervised and not directly linked to the caller, they cannot be waited on start_link/1, unlike async/1, returns {:ok, pid} (which is the result expected by supervision trees)

by default, most supervision strategies will try to restart a worker after it exits regardless of reason. if you design the task to terminate normally, consider passing restart: :transient in the options to worker/3

dynamically supervised tasks

the Task.Supervisor module allows developers to dynamically create multiple supervised tasks

example:

{:ok, pid} = Task.Supervisor.start_link() t = Task.Supervisor.async(pid, fn -> # Do something end) Task.await(t)

to add the task supervisor to your supervision tree:

children = [ supervisor(Task.Supervisor, [[name: MyApp.TaskSupervisor]]) ]

and now you can dynamically start supervised tasks:

Task.Supervisor.start_child(MyApp.TaskSup, fn -> # Do something end)

or even use the async/await pattern:

Task.Supervisor.async(MyApp.TaskSuper, fn -> # Do something end) |> Task.await()

distributed tasks

to use a task supervisor to dynamically spawn tasks across nodes:

# In the remote node Task.Supervisor.start_link(name: MyApp.DistSuper)

# In the client Task.Supervisor.async({MyApp.DistSuper, :remote@local}, MyMod, :my_fun, [arg1, arg2, arg3])

Note that, when working with distributed tasks, one should use the async/4 function that expects explicit MFA, instead of async/2 that works with anonymous functions That’s because anonymous functions expect the same module version to exist on all involved nodes