class_docstr: Multi step soft actor critic.
  
  :param algorithm_config: Algorithm options.
  :param learner_model: Structured torch actor critic to train.
  :param distributed_actors: Distributed actors for collection of training rollouts.
  :param model_selection: Optional model selection class, receives model evaluation results.
  :param evaluator: The evaluator to use.
class_docstr_tok: ["Multi","step","soft","actor","critic",".",":","param","algorithm_config",":","Algorithm","options",".",":","param","learner_model",":","Structured","torch","actor","critic","to","train",".",":","param","distributed_actors",":","Distributed","actors","for","collection","of","training","rollouts",".",":","param","model_selection",":","Optional","model","selection","class",",","receives","model","evaluation","results",".",":","param","evaluator",":","The","evaluator","to","use","."]
docstr: Train policy using the synchronous advantage actor critic.
  
  :param n_epochs: number of epochs to train.
docstr_tok: ["Train","policy","using","the","synchronous","advantage","actor","critic",".",":","param","n_epochs",":","number","of","epochs","to","train","."]
code: def _train_async(self, n_epochs) -> None:
      """Train policy using the synchronous advantage actor critic.
  
      :param n_epochs: number of epochs to train.
      """
  
      # run training epochs
      if n_epochs <= 0:
          n_epochs = sys.maxsize
      epoch_length = self.algorithm_config.epoch_length
      patience = self.algorithm_config.patience
  
      # Perform a hard update on the critic
      self.learner_model.critic.update_target_weights(1.0)
  
      # run training epochs
      for epoch in range(n_epochs):
          start = time.time()
          print("Update epoch - {}".format(epoch))
  
          # compute evaluation reward
          reward = -np.inf
          if self.evaluator:
              self.evaluate()
          # take training reward and notify model selection
          else:
              if epoch > 0:
                  prev_reward = reward
                  try:
                      reward = self.distributed_workers.get_stats_value(
                          BaseEnvEvents.reward,
                          LogStatsLevel.EPOCH,
                          name="mean",
                      )
                  except KeyError:
                      reward = prev_reward
  
              # best model selection
              self.model_selection.update(reward)
  
          # evaluate policy
          time_evaluation = time.time() - start
  
          # early stopping
          if (
              patience
              and self.model_selection.last_improvement > patience
          ):
              BColors.print_colored(
                  "-> no improvement since {} epochs: EARLY STOPPING!".format(
                      patience
                  ),
                  color=BColors.WARNING,
              )
              increment_log_step()
              break
  
          time_deq_actors = 0
          time_before_update = time.time()
          for epoch_step_idx in range(epoch_length):
              (
                  q_size_before,
                  q_size_after,
                  time_deq_actors,
              ) = self.distributed_workers.collect_rollouts()
  
              # Record the queue sizes
              self.events.estimated_queue_sizes(
                  after=q_size_after, before=q_size_before
              )
  
              # policy update
              for batch_updates in range(
                  self.algorithm_config.num_batches_per_iter
              ):
                  self._update()
                  total_num_batch_updates = (
                      batch_updates
                      + epoch_step_idx
                      * self.algorithm_config.num_batches_per_iter
                      + (
                          epoch_length
                          * self.algorithm_config.num_batches_per_iter
                      )
                      * epoch
                  )
                  if (
                      total_num_batch_updates
                      % self.algorithm_config.target_update_interval
                      == 0
                  ):
                      self.learner_model.critic.update_target_weights(
                          self.algorithm_config.tau
                      )
  
                  self.distributed_workers.broadcast_updated_policy(
                      self.learner_model.policy.state_dict()
                  )
          time_updating = time.time() - time_before_update
  
          total_time = time.time() - start
          self.events.time_dequeuing_actors(
              time=time_deq_actors, percent=time_deq_actors / total_time
          )
  
          # Buffer events
          self.events.buffer_size(
              len(self.distributed_workers.replay_buffer)
          )
          self.events.buffer_avg_pick_per_transition(
              value=self.distributed_workers.replay_buffer.cum_moving_avg_num_picks
          )
  
          # increase step counter (which in turn triggers the log statistics writing)
          increment_log_step()
  
          print("Time required for epoch: {:.2f}s".format(total_time))
          print(
              " - total ({} steps) updating: {:.2f}s ({:.2f}%), mean time/step: {:.2f}s".format(
                  epoch_length
                  * self.algorithm_config.num_batches_per_iter,
                  time_updating,
                  time_updating / total_time,
                  time_updating
                  / (
                      epoch_length
                      * self.algorithm_config.num_batches_per_iter
                  ),
              )
          )
          print(
              " - total time evaluating the model: {:.2f}s ({:.2f}%)".format(
                  time_evaluation, time_evaluation / total_time
              )
          )
  
imports: import sys
  import time
  from typing import Union, Optional, BinaryIO, Dict, Tuple, List
  import numpy
  import torch
  import torch.nn
  from gym import spaces
  from maze.core.agent.torch_actor_critic import TorchActorCritic
  from maze.core.annotations import override
  from maze.core.env.base_env_events import BaseEnvEvents
  from maze.core.env.structured_env import ActorID
  from maze.core.log_stats.log_stats import increment_log_step, LogStatsLevel
  from maze.core.trajectory_recording.records.structured_spaces_record import StructuredSpacesRecord
  from maze.train.trainers.common.evaluators.rollout_evaluator import RolloutEvaluator
  from maze.train.trainers.common.model_selection.best_model_selection import BestModelSelection
  from maze.train.trainers.common.trainer import Trainer
  from maze.train.utils.train_utils import compute_gradient_norm
  from maze.utils.bcolors import BColors
  from maze.train.parallelization.distributed_actors.base_distributed_workers_with_buffer import BaseDistributedWorkersWithBuffer
  from maze.train.trainers.sac.sac_algorithm_config import SACAlgorithmConfig
  from maze.train.trainers.sac.sac_events import SACEvents
  from torch.distributions.utils import logits_to_probs
label_desc: Use image node_id 7 for calling the SAC obj's underlying member method code with example usage: obj._train_async(n_epochs) without return types