1

This code is supposed to control a servo from stdin


import asyncio
import sys
import threading
from multiprocessing import Process
async def connect_stdin_stdout():
    loop = asyncio.get_event_loop()
    reader = asyncio.StreamReader()
    protocol = asyncio.StreamReaderProtocol(reader)
    await loop.connect_read_pipe(lambda: protocol, sys.stdin)
    w_transport, w_protocol = await loop.connect_write_pipe(asyncio.streams.FlowControlMixin, sys.stdout)
    writer = asyncio.StreamWriter(w_transport, w_protocol, reader, loop)
    return reader, writer
servo0ang = 90
async def main():
    reader, writer = await connect_stdin_stdout()
    while True:
        res = await reader.read(100)
        if not res:
            break
        servo0ang = int(res)
# Main program logic follows:
def runAsync():
    asyncio.run(main())
def servoLoop():
    pwm = Servo()
    while True:
        pwm.setServoPwm('0', servo0ang)
if __name__ =="__main__":
    p = Process(target = servoLoop)
    p.start()
    runAsync()
    p.join()

When i run it the async function starts but servoLoop doesn't

It was supposed to turn the servo to the angle specified in stdin. I'm a bit rusty at Python.

The Servo class is from an example program that came with the robot I'm working with and it works there

  • 2
    It's tricky combining `asyncio` and `multiprocessing`. See [What kind of problems (if any) would there be combining asyncio with multiprocessing?](https://stackoverflow.com/questions/21159103/what-kind-of-problems-if-any-would-there-be-combining-asyncio-with-multiproces). – craigb Nov 04 '22 at 00:20
  • In addition to craigb comment, I don't know what `Servo` is, and what its init method does, but it appears that in the servoLoop process, you just endlessly set the angle to 90. The fact that the main process may in the meantime change its own `servo0ang` variable won't change that. They have their own `servo0ang` each. It is not shared memory. – chrslg Nov 04 '22 at 00:35
  • Please clarify your specific problem or provide additional details to highlight exactly what you need. As it's currently written, it's hard to tell exactly what you're asking. – Community Nov 04 '22 at 01:11
  • @chrslg How do I share that value to the process. – LinuxMintRocks Nov 04 '22 at 01:28
  • @craigb The only thing other than a placeholder in the "blocking_func" in the bad code example is a return statement, which my child process doesn't use(It is a while true loop constantly feeding the pwm controller the value from a variable because it needs that) – LinuxMintRocks Nov 04 '22 at 01:34

1 Answers1

0

So, as I said in comment, you are not sharing servo0ang. You have two processes. Each of them has its own variables. They have the same name, and the same initial value, as in a fork in other languages. Because the new process starts as a copy of the main one. But they are just 2 different python running, with almost nothing to do which each other (one if the parent of the other, so can join it).

If you need to share data, you have either to send them through pipes connecting the two processes. Or by creating a shared memory that both process will access to (it seems easy. And in python, it is quite easy. But it is also easy to have some inefficient polling systems, like yours seems to be, with its infinite loop trying to poll values of servo0ang as fast as it can to not miss any change. In reality, very often, it would be a better idea to wait on pipes. But well, I won't discuss the principles of your project. Just how to do what you want to do, not whether it is a good idea or not).

In python, you have, in the multiprocessing module a Value class that creates memory that can then be shared among processes of the same machine (with Manager you could even share value among processes of different machines, but that is slower)

from multiprocessing import Process, Value
import time # I don't like infinite loop without sleep

v=Value('i',90) # Creates an integer, with initial value of 90 in shared memory
x=90 # Just a normal integer, by comparison
print(v.value,x) # read it 
v.value=80 # Modify it
x=80

def f(v):
    global x
    while True:
        time.sleep(1)
        v.value = (v.value+1)%360
        x = (x+1)%360

p=Process(target=f, args=(v,))
p.start()
while True:
    print("New val", v.value, x)
    time.sleep(5)

As you see, the value in the main loop increases approx. 5 at each loop. Because the process running f increased it by 1 5 times in the meantime. But x in that same loop doesn't change. Because it is only the x of the process that runs f (the same global x, but different process. It is as you were running the same program, twice, into two different windows) that changes.

Now, applied to your code

import asyncio
import sys
import threading
import time
from multiprocessing import Process, Value
async def connect_stdin_stdout():
    loop = asyncio.get_event_loop()
    reader = asyncio.StreamReader()
    protocol = asyncio.StreamReaderProtocol(reader)
    await loop.connect_read_pipe(lambda: protocol, sys.stdin)
    w_transport, w_protocol = await loop.connect_write_pipe(asyncio.streams.FlowControlMixin, sys.stdout)
    writer = asyncio.StreamWriter(w_transport, w_protocol, reader, loop)
    return reader, writer
servo0ang = Value('i', 90)
async def main():
    reader, writer = await connect_stdin_stdout()
    while True:
        res = await reader.read(100)
        if not res:
            break
        servo0ang.value = int(res)
# Main program logic follows:
def runAsync():
    asyncio.run(main())

class Servo:
    def setServoPwm(self, s, ang):
        time.sleep(1)
        print(f'\033[31m{ang=}\033[m')

def servoLoop():
    pwm = Servo()
    while True:
        pwm.setServoPwm('0', servo0ang.value)
if __name__ =="__main__":
    p = Process(target = servoLoop)
    p.start()
    runAsync()
    p.join()

I used a dummy Servo class that just prints in red the servo0ang value.

Note that I've change nothing in your code, outside that. Which means, that, no, asyncio.run was not blocking the other process. I still agree with comments you had, on the fact that it is never great to combine both asyncio and processes. Here, you have no other concurrent IO, so your async/await is roughly equivalent to a good old while True: servo0ang.value=int(input()). It is not like your input could yield to something else. There is nothing else, at least not in this process (if your two processes were communicating through a pipe, that would be different)

But, well how ever vainly convoluted your code may be, it works, and asyncio.run is not blocking the other process. It is just that the other process was endlessly calling setPwm with the same, constant, 90 value, that could never change, since that process was doing nothing else with this variable than calling setPwm with. It was doing nothing to try do grab a new value from the main process.

With Value shared memory, there is nothing to do neither. But this time, since it is shared memory, it is less vain to expect that the value changes when nothing changes it in the process.

chrslg
  • 9,023
  • 5
  • 17
  • 31