class_docstr: A class the contains a naive implementation for concurrent sub-process
  execution. It uses `subprocess.Popen` and threads to read from stdout and
  stderr of the subprocess, and to write to stdin of the subprocess.
  
  All read data and timeouts are passed to a protocol instance, which can
  create the final result.
class_docstr_tok: ["A","class","the","contains","a","naive","implementation","for","concurrent","sub-process","execution",".","It","uses","`","subprocess.Popen","`","and","threads","to","read","from","stdout","and","stderr","of","the","subprocess",",","and","to","write","to","stdin","of","the","subprocess",".","All","read","data","and","timeouts","are","passed","to","a","protocol","instance",",","which","can","create","the","final","result","."]
code: def process_loop(self) -> dict:
      # Process internal messages until no more active file descriptors
      # are present. This works because active file numbers are only
      # removed when an EOF is received in `self.process_queue`.
      while self.should_continue():
          self.process_queue()
  
      # Let the protocol prepare the result. This has to be done after
      # the loop was left to ensure that all data from stdout and stderr
      # is processed.
      self.result = self.protocol._prepare_result()
      self.protocol.process_exited()
  
      # Ensure that all communication channels are closed.
      self.ensure_stdin_stdout_stderr_closed()
      self.protocol.connection_lost(None)  # TODO: check exception
      self.wait_for_threads()
      self._set_process_exited()
      return self.result
  
imports: from __future__ import annotations
  import enum
  import logging
  import subprocess
  import threading
  import time
  from collections import deque
  from collections.abc import Generator
  from queue import Empty, Queue
  from subprocess import Popen
  from typing import IO, Any, Optional
  from datalad.utils import on_windows
  from .exception import CommandError
  from .protocol import GeneratorMixIn, WitlessProtocol
  from .runnerthreads import IOState, ReadThread, WaitThread, WriteThread, _try_close
label_desc: Use image node_id 5 for calling the ThreadedRunner obj's underlying member method code with example usage: obj.process_loop() and returns: self