proof of concept: alternate asynchronous non-blocking input

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Post Reply
Message
Author
nephi12
Posts: 3
Joined: 26 Jan 2017 12:50

proof of concept: alternate asynchronous non-blocking input

#1 Post by nephi12 » 26 Jan 2017 13:00

I have been trying for a long time to come up with a way to get non-blocking input without writing to a file, just because :)
well, I succeeded, so I thought I would share.
Here is a (relatively simple) proof of concept which starts two processes, one which handles main logic and output, and one which handles input.

There are many things which I have plans to improve, but this is the basic idea.

Hope someone likes it! :)

Note: I would suggest running it in it's own new window, as It doesn't exit gracefully.

Code: Select all

@echo off & setlocal enabledelayedexpansion

cls

if /I {%1}=={input} goto input
if /I {%1}=={output} goto output

title 1234567890987654321

for /f "tokens=1,2 delims=:" %%i in ('tasklist /fi "windowtitle eq 1234567890987654321" /fo list') do (
    if "%%i"=="PID" set "my_pid=%%j"
)

set my_pid=%my_pid: =%

start /b "" cmd /c "%0" input > nul
start /b /wait "" cmd /c "%0" output < nul
exit /b

:input

set key=

for /f "delims=" %%i in ('xcopy /w "%~f0" "%~f0" 2^> nul') do (
    if not defined key set "key=%%i"
)

set "key=%key:~-1%"

title %key%

goto input


:output

for /f "tokens=1,2 delims=:" %%i in ('tasklist /v /fi "pid eq %my_pid%" /fo list') do (
   if "%%i"=="Window Title" set "key=%%j"
)
set key=%key: =%

if defined key (
   if "!key:~0,1!"=="!key!" (
      echo main: !key!
   )
)

goto output

penpen
Expert
Posts: 1991
Joined: 23 Jun 2013 06:15
Location: Germany

Re: proof of concept: alternate asynchronous non-blocking input

#2 Post by penpen » 26 Jan 2017 17:43

I think it is a unconventional but nice idea to use the window title to transfer data between processes:
This idea has the advantage, that the processes don't need to know each other when starting.
('Only' an "initiating" window title to get the processes to know each other.).

I think i like that idea! :D

Although i would implement unblocking input in a completely different way:
I would probably try to add a "spamming" process writing to the same pipe as the blocking input process.

Something like this: (Warning: The following batch file is not tested properly and may need to create a file "terminate" manually - too late for today.)

Code: Select all

:: "unblockingInput.bat"
:: non-blocking keyboard input implementation
@echo off
setlocal enableExtensions enableDelayedExpansion
set "quit=q"
set "unblock=x"
set "SignalTerminate=terminate"

if "%~1" == "" (
   if exist "!SignalTerminate!" >nul del "!SignalTerminate!"
        if exist "!SignalTerminate!" (
      echo(Unexpected Error: "!SignalTerminate!" is write protected, or a directory.
   ) else (
      echo: Starting unblocking input:
      (((>&2 "%~f0" input) | (>&2 "%~f0" unblock)) 2>&1) | ("%~f0" main)
      if exist "!SignalTerminate!" >nul del "!SignalTerminate!"
   )
) else call :%~1
endlocal
goto :eof


:input
for /L %%a in (0, 0, 0) do (
   if exist "!SignalTerminate!" exit 0
   set "key="
   for /f "delims=" %%b in ('xcopy /w "%~f0" "%~f0" 2^>nul') do if not defined key set "key=%%b"
   set "key=!key:~-1!"
   <nul set /P "=!key!"
)
echo(Unexpected termination of endless loop on :input.
exit 1


:unblock
for /L %%a in (0, 0, 0) do (
   <nul set /P "=!unblock!"
   if exist "!SignalTerminate!" exit 0
)
echo(Unexpected termination of endless loop on :unblock.
exit 1


:main
>&3 echo(Awaiting input key ^^^('!quit!' to quit^^^):
for /L %%a in (0, 0, 0) do (
   set "input="
   set /P "input="

   if defined input set "input=!input:%unblock%=!"
   if defined input echo(Read key: !input!

   for /f "tokens=1* delims=%quit%" %%b in ("_!input!_!quit!_") do if "%%~c" == "_!quit!_" (
      >nul copy nul "!SignalTerminate!"
      call :outro
      exit 0
   )
)
echo(Unexpected termination of endless loop on :main.
exit 1


:outro
echo(
echo(Bye world^^^!
echo(Press any key to continue.
goto :eof


penpen

dbenham
Expert
Posts: 2461
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: proof of concept: alternate asynchronous non-blocking input

#3 Post by dbenham » 26 Jan 2017 19:14

OK, so you can transfer a single message (single character in this case). But how are you to know when the title has a new message? The reader cannot clear the value from the writer's title, so the next time it checks and sees the same value, it cannot determine if it is a replicate new value, or the original old value.

You could have the reader signal the writer that it received the message via the reader's title, but then you are no longer fully asynchronous. The reader can continue to do work until it decides to poll for data, but the writer cannot receive and write the next key-press until it has gotten the OK from the reader that the last message was received.

In order to have fully asynchronous communication, you need some kind of buffer (queue) that the writer can write to at will, and the reader can read from at will. The two batch processes cannot share memory, so I don't see any good alternative to a file.


Dave Benham

nephi12
Posts: 3
Joined: 26 Jan 2017 12:50

Re: proof of concept: alternate asynchronous non-blocking input

#4 Post by nephi12 » 27 Jan 2017 10:02

The input loop can read the current title and append it's message with an agreed upon delimiter. When the main loop reads the title, it stores the whole thing in a variable and sets it to blank. it then takes input from this variable until it is empty, at which point it reads from the title again.

Or it could be something like the main process reads the title into a variable, takes off the first character, and puts the string back into the title. Then the input loop reads the current title and appends a character to it, putting it back.

However, the reason I didn't implement it this way is because I'm actually looking for exactly that, I'm reading direction keys, so I just need to move in the direction of the last pressed key.

pieh-ejdsch
Posts: 239
Joined: 04 Mar 2014 11:14
Location: germany

Re: proof of concept: alternate asynchronous non-blocking input

#5 Post by pieh-ejdsch » 27 Jan 2017 10:06

for a Little bit asyncron mode more

Code: Select all

@echo off
setlocal
title :,0,"
if /I {%1}=={input} goto input
if /I {%1}=={output} goto output

 rem use TAB to EXIT
for /f "delims= " %%T in ('robocopy /L . . /njh /njs' )do set "TAB=%%T"
set "title=1234567890987654321"
title %title%

for /f "tokens=2delims=," %%i in ('tasklist /fi "windowtitle eq %title%" /fo csv /nH' )do set "my_pid=%%~i"
set/aN=1,X=0
start /b "" cmd /c "%0" input
call "%0" output <nul
echo END script
pause
exit /b

:input
set "K="
for /f "eol=0 delims=" %%i in ('xcopy /w "%~f0." ?' )do set "K=%%i"
 setlocal enabledelayedexpansion
 if NOT :!K:~-1!==: title :,!N!,"!K:~-1!
 if !TAB!==!K:~-1! exit /b
 endlocal
 set/aN%%=10000,N+=1
goto :input

:output
for /f "tokens=10*delims=," %%i in ('tasklist /v /fi "pid eq %my_pid%" /fo csv /nH' )do (
  if NOT %X%==%%i set/aX=%%i&echo --- %%~j ---
  if ^%TAB%==%%~j exit /b
  goto :output
)

Phil

nephi12
Posts: 3
Joined: 26 Jan 2017 12:50

Re: proof of concept: alternate asynchronous non-blocking input

#6 Post by nephi12 » 27 Jan 2017 10:23

Phil, I really like the changes you made, really smart way to make it more asynchronous. May I use it in my project?
Thanks.

Sponge Belly
Posts: 216
Joined: 01 Oct 2012 13:32
Location: Ireland
Contact:

Re: proof of concept: alternate asynchronous non-blocking input

#7 Post by Sponge Belly » 01 Mar 2017 09:41

Hi Phil,

I didn’t know you could coax a tab from robocopy like that. Thanks for the tip! :)

- SB

T3RRY
Posts: 243
Joined: 06 May 2020 10:14

Re: proof of concept: alternate asynchronous non-blocking input

#8 Post by T3RRY » 06 Sep 2021 06:24

pieh-ejdsch wrote:
27 Jan 2017 10:06
for a Little bit asyncron mode more

Code: Select all

@echo off
setlocal
title :,0,"
if /I {%1}=={input} goto input 
if /I {%1}=={output} goto output

 rem use TAB to EXIT
for /f "delims= " %%T in ('robocopy /L . . /njh /njs' )do set "TAB=%%T"
set "title=1234567890987654321"
title %title%

for /f "tokens=2delims=," %%i in ('tasklist /fi "windowtitle eq %title%" /fo csv /nH' )do set "my_pid=%%~i"
set/aN=1,X=0
start /b "" cmd /c "%0" input
call "%0" output <nul
echo END script
pause
exit /b

:input
set "K="
for /f "eol=0 delims=" %%i in ('xcopy /w "%~f0." ?' )do set "K=%%i"
 setlocal enabledelayedexpansion
 if NOT :!K:~-1!==: title :,!N!,"!K:~-1!
 if !TAB!==!K:~-1! exit /b
 endlocal
 set/aN%%=10000,N+=1
goto :input

:output
for /f "tokens=10*delims=," %%i in ('tasklist /v /fi "pid eq %my_pid%" /fo csv /nH' )do (
  if NOT %X%==%%i set/aX=%%i&echo --- %%~j ---
  if ^%TAB%==%%~j exit /b
  goto :output
)
Phil
Very nice - I've made a few changes in order to better handle Carriage Return and backspace, as well as converting the goto loops to for /l infinite loops.

Code: Select all

@echo off

:: Asynchronous key reading script for taking non blocking input
:: Original Author: Nephi12
:: Contributer: Phil aka: pieh-ejdsch
:: Contributer: T3RRY
:: https://www.dostips.com/forum/viewtopic.php?t=7679
:: Handles the following Keys [Including Upper and Lower Case]:
:: `~1!2@3#4$5%6^7&8*9(0)-_=+\|]}[{poiuytrewqsadfghjkl;:'"/?.>,<mnbvcxz
:: TAB is used to exit the script.

 setlocal
 title :,0,"
:: thread label control structure by Jeb - https://stackoverflow.com/a/68636825/12343998
 for /F "tokens=3 delims=:" %%L in ("%~0") do goto %%L

(Set LF=^


%= Do not modify LineFeed var =%)

 rem use TAB to EXIT
 for /f "delims= " %%T in ('robocopy /L . . /njh /njs' )do set "TAB=%%T"

 set "title=1234567890987654321"
 title %title%

 for /f "tokens=2 delims=," %%i in ('tasklist /fi "windowtitle eq %title%" /fo csv /nH' )do set "my_pid=%%~i"
 set/aN=1
 Start /AboveNormal /b "" "%~d0\:Input:\..%~pnx0"
 Start /AboveNormal /b /Wait "" "%~d0\:Output:\..%~pnx0" <nul
 echo END script
 pause
exit /b

:input
For /l %%. in ()Do (%= Do not modify this function =%
 set "K="
 for /f "eol=0 delims=" %%i in ('xcopy /w "%~f0." ?' )do set "K=%%i"
  setlocal enabledelayedexpansion
  if NOT :!K:~-1!==: title :,!N!,"!K:~-1!
  if !TAB!==!K:~-1! EXIT
  set/aN%%=10000
  For %%v in (!N!)Do endlocal & Set/A"N=%%v+1"
 )
)

:output

 For /F %%a in ('copy /Z "%~dpf0" nul') do (set "CR=%%a") %= Enable use of Carriage Return =%
 For /F "delims=#" %%a in ('"prompt #$H# &echo on &for %%b in (1) do rem"') do (
  Set "BS=%%a" %= Enable use of BackSpace =%
 )
 Set "BS=%BS:~0,1%"
 Setlocal DisableDelayedExpansion

 For /L %%. in ()Do (%= Do not modify outer For loops =%
  For /f "tokens=10* delims=," %%i in ('tasklist /v /fi "pid eq %my_pid%" /fo csv /nH' )do (
   For /f "tokens=1,2 Delims=," %%J in ("%%~j")Do (
    Set "Input#=%%~J"
    Set "Key=%%~K"
    Setlocal EnableDelayedExpansion
    If not "!Input#!"=="!LastInput#!" (%= Perform actions based on input =%
     If "!Key!"==" " (%= Optional Condition - Perform action on SPACE =%
      Set "key-lock=1"
      <nul set /P"=.!BS! "
     )
     If "!Key!"=="!CR!" (%= Optional Condition - Perform action on ENTER =%
      Set "key-lock=1"
      Echo(
     )
     If "!Key!"=="!BS!" (%= Optional condition - Perform action on BACKSPACE =%
      Set "key-lock=1"
      <nul set /p"=!BS! !BS!"
     )
     If defined Key (%= Input was non-comma =%
      If not defined key-lock (%= Perform action using non COMMA SPACE ENTER or BACKSPACE keypress =%
       <nul set /p"=!Key!"
      )
     )Else If not "!Input#!"=="0" (%= Optional condition - Perfom action on COMMA =%
      %= Input was COMMA [ removed in For /F J set ] =%
      <nul set /p"=,"
     )
    )Else (%= No Input was entered during current loop =%
     Set "Key="
     rem %= Perform any command/s while waiting for next input =%
    )
   )
   %= EXAMPLE - define variables to retain between loops by defining to retain list =%
   %= LastInput# Definition required =%
   Set "Retain="LastInput#=!Input#!" "Keys=!Keys!!key!""
   if !TAB!==!Key! (%= Hard exit of process required due to infinite for /l loop =%
    %= Example demonstration of Tunneled retain variables =% Echo(!Keys!
    Endlocal
    EXIT
   )
   %= Tunnel list of return variables [ '*' and '?' cannot be parsed. ] =%
   For /f "Delims=" %%v in ("!Retain!")Do Endlocal & For %%R in (%%v)Do Set %%R
  )
 )
)

Post Reply