问题
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