class_docstr: Generator returned by run_command if the protocol class
  is a subclass of `datalad.runner.protocol.GeneratorMixIn`
class_docstr_tok: ["Generator","returned","by","run_command","if","the","protocol","class","is","a","subclass","of","`","datalad.runner.protocol.GeneratorMixIn","`"]
code: def _locked_send(self, message):
      if self.state == self.GeneratorState.initialized:
          if message is not None:
              raise RuntimeError(
                  f"sent non-None message {message!r} to initialized generator "
              )
          self.state = self.GeneratorState.process_running
  
      runner = self.runner
  
      if self.state == self.GeneratorState.process_running:
          # If we have elements in the result queue, return one
          while (
              len(self.result_queue) == 0 and runner.should_continue()
          ):
              runner.process_queue()
          if len(self.result_queue) > 0:
              return self.result_queue.popleft()
  
          # The process must have exited
          # 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.
          runner.protocol.process_exited()
          self.return_code = runner.process.poll()
          self._check_result()
          self.state = self.GeneratorState.process_exited
  
      if self.state == self.GeneratorState.process_exited:
          # The protocol might have added result in the
          # _prepare_result()- or in the process_exited()-
          # callback. Those are returned here.
          if len(self.result_queue) > 0:
              return self.result_queue.popleft()
          runner.ensure_stdin_stdout_stderr_closed()
          runner.protocol.connection_lost(
              None
          )  # TODO: check for exceptions
          runner.wait_for_threads()
          runner._set_process_exited()
          self.state = self.GeneratorState.connection_lost
  
      if self.state == self.GeneratorState.connection_lost:
          # Get all results that were enqueued in
          # state: GeneratorState.process_exited.
          if len(self.result_queue) > 0:
              return self.result_queue.popleft()
          self.state = self.GeneratorState.exhausted
          runner.owning_thread = None
          with runner.generator_condition:
              runner.generator = None
              runner.generator_condition.notify()
  
      if self.state == self.GeneratorState.exhausted:
          raise StopIteration(self.return_code)
  
      raise RuntimeError(f"unknown state: {self.state}")
  
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 4 for calling the _ResultGenerator obj's underlying member method code with example usage: obj._locked_send(message) and returns: self, self, self