Getting user input in an elixir Task

喜欢而已 提交于 2019-12-11 01:04:50

问题


I'm trying to write a simple elixir program that is able to react to user input. My problem is that reading from stdio doesn't seem to work from Tasks. If my whole idea is silly please show me an example on how it's done. I can't find anything in the web

I broke my problem down to a simple example:

t = Task.async((fn->IO.gets "what?" end))                
%Task{owner: #PID<0.65.0>, pid: #PID<0.80.0>, ref: #Reference<0.0.2.135>}

The Task is started:

iex(4)> pid=Map.get(t, :pid)
#PID<0.80.0>
iex(5)> Process.alive? pid                                       
true 

and alive but it doesn't print to stdio nor does it read. It's not exiting normally or with án exception. I tried IO.read/2 too.

In my program the Task is started with Task.spawn_link/1 but the problem is the same.IO.gets/2 and code following the IO.gets/2 function is not executed.

The Supervisor starting the Task:

defmodule Prime do
  use Application

    def start(_type, _args) do
       import Supervisor.Spec, warn: false

children = [
  # Define workers and child supervisors to be supervised
  worker(Task, [fn->Prime.IO.communicate(nil) end], restart: :transient),
  supervisor(Prime.Test.Supervisor, [])
]

opts = [strategy: :one_for_one, name: Prime.Supervisor]
Supervisor.start_link(children, opts) end end

The tasks function:

defmodule Prime.IO do

@doc """
    handles communication with the user and user demanded Actions.
"""

def communicate(numTasks) do
    case(numTasks) do

    nil ->
        {numTasks, _} =Integer.parse(IO.gets "This program searches for prime numbers per try and error.\nHow many concurrent Tasks?\n")
        Prime.IO.communicate(numTasks)

    x when is_number(x) ->
        Prime.Test.Server.setTaskNumber(numTasks)
        Prime.IO.communicate("waiting")

    y when is_bitstring(y) ->
        IO.puts(numTasks)

    _ -> 
        Prime.IO.communicate(nil)

    end
end
end 

回答1:


In Elixir there is always one process handling the io. If you print something in one of processes, it doesn't directly write to stdout. It sends a message to a process called "group leader". Even if many processes write at the same time you can't see messages mixed up like this:

This is This is message one
message two

You will always get clean output:

This is message one
This is message two

In case of input, there can be only one process reading from stdin. If you are running iex session, this the shell process. If other processes want to read from stdin, they wait patiently until iex gives up control over stdin.

That is why, if you call Task.await the function magically works. It is not because await starts the process. Await under the hood calls receive which suspends the process that called it until a message comes in. Shell gives up stdin and now other processes can use it, so you see the prompt.

By default await waits 5 seconds for results, so you have to be quick.

All those problems are simply because you are running interactive session. If you run it normally, things should just work. When you are waiting for the task, you can specify infinite timeout like this:

result = Task.await(t, :infinity)

This is not documented in official Task docs, but almost all OTP timeouts respect the convention with passing infinity as an atom.




回答2:


This is the minimum code that would work:

t = Task.async(fn -> IO.gets "What?" end)
Task.await(t)

Basically you need to await the returned task. There's more about async and await here: http://elixir-lang.org/docs/stable/elixir/Task.html#await/2



来源:https://stackoverflow.com/questions/36407736/getting-user-input-in-an-elixir-task

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!