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","."]
docstr: Run the command as specified in __init__.
  
  This method is not re-entrant. Furthermore, if the protocol is a
  subclass of ``GeneratorMixIn``, and the generator has not been
  exhausted, i.e. it has not raised `StopIteration`, this method should
  not be called again. If it is called again before the generator is
  exhausted, a ``RuntimeError`` is raised. In the non-generator case, a
  second caller will be suspended until the first caller has returned.
  
  Returns
  -------
  Any
      If the protocol is not a subclass of ``GeneratorMixIn``, the
      result of protocol._prepare_result will be returned.
  
  Generator
      If the protocol is a subclass of ``GeneratorMixIn``, a Generator
      will be returned. This allows to use this method in constructs
      like::
  
          for protocol_output in runner.run():
              ...
  
      Where the iterator yields whatever protocol.pipe_data_received
      sends into the generator.
      If all output was yielded and the process has terminated, the
      generator will raise StopIteration(return_code), where
      return_code is the return code of the process. The return code
      of the process will also be stored in the "return_code"-attribute
      of the runner. So you could write::
  
          gen = runner.run()
          for file_descriptor, data in gen:
              ...
  
          # get the return code of the process
          result = gen.return_code
docstr_tok: ["Run","the","command","as","specified","in","__init__",".","This","method","is","not","re-entrant",".","Furthermore",",","if","the","protocol","is","a","subclass","of","``","GeneratorMixIn","``",",","and","the","generator","has","not","been","exhausted",",","i.e",".","it","has","not","raised","`","StopIteration","`",",","this","method","should","not","be","called","again",".","If","it","is","called","again","before","the","generator","is","exhausted",",","a","``","RuntimeError","``","is","raised",".","In","the","non-generator","case",",","a","second","caller","will","be","suspended","until","the","first","caller","has","returned",".","Returns","--","--","--","-","Any","If","the","protocol","is","not","a","subclass","of","``","GeneratorMixIn","``",",","the","result","of","protocol._prepare_result","will","be","returned",".","Generator","If","the","protocol","is","a","subclass","of","``","GeneratorMixIn","``",",","a","Generator","will","be","returned",".","This","allows","to","use","this","method","in","constructs","like",":",":","for","protocol_output","in","runner.run","(",")",":","...","Where","the","iterator","yields","whatever","protocol.pipe_data_received","sends","into","the","generator",".","If","all","output","was","yielded","and","the","process","has","terminated",",","the","generator","will","raise","StopIteration","(","return_code",")",",","where","return_code","is","the","return","code","of","the","process",".","The","return","code","of","the","process","will","also","be","stored","in","the","``","return_code","''","-attribute","of","the","runner",".","So","you","could","write",":",":","gen","=","runner.run","(",")","for","file_descriptor",",","data","in","gen",":","...","#","get","the","return","code","of","the","process","result","=","gen.return_code"]
code: def run(self) -> dict | _ResultGenerator:
      """
      Run the command as specified in __init__.
  
      This method is not re-entrant. Furthermore, if the protocol is a
      subclass of ``GeneratorMixIn``, and the generator has not been
      exhausted, i.e. it has not raised `StopIteration`, this method should
      not be called again. If it is called again before the generator is
      exhausted, a ``RuntimeError`` is raised. In the non-generator case, a
      second caller will be suspended until the first caller has returned.
  
      Returns
      -------
      Any
          If the protocol is not a subclass of ``GeneratorMixIn``, the
          result of protocol._prepare_result will be returned.
  
      Generator
          If the protocol is a subclass of ``GeneratorMixIn``, a Generator
          will be returned. This allows to use this method in constructs
          like::
  
              for protocol_output in runner.run():
                  ...
  
          Where the iterator yields whatever protocol.pipe_data_received
          sends into the generator.
          If all output was yielded and the process has terminated, the
          generator will raise StopIteration(return_code), where
          return_code is the return code of the process. The return code
          of the process will also be stored in the "return_code"-attribute
          of the runner. So you could write::
  
              gen = runner.run()
              for file_descriptor, data in gen:
                  ...
  
              # get the return code of the process
              result = gen.return_code
  
      """
      with self.initialization_lock:
          return self._locked_run()
  
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 3 for calling the ThreadedRunner obj's underlying member method code with example usage: obj.run() and returns: self