diff --git a/core/modules/views/src/Plugin/views/style/StylePluginBase.php b/core/modules/views/src/Plugin/views/style/StylePluginBase.php
index 3f555cfec9fd0bc400255d97c35a3953e8972e39..3f55c28be780d38944978728ffab99ae4cf2cbc1 100644
--- a/core/modules/views/src/Plugin/views/style/StylePluginBase.php
+++ b/core/modules/views/src/Plugin/views/style/StylePluginBase.php
@@ -9,6 +9,7 @@
 use Drupal\views\Plugin\views\PluginBase;
 use Drupal\views\Plugin\views\wizard\WizardInterface;
 use Drupal\views\Render\ViewsRenderPipelineMarkup;
+use Drupal\views\ResultRow;
 use Drupal\views\ViewExecutable;
 
 /**
@@ -508,9 +509,40 @@ public function renderGroupingSets($sets) {
       // Render as a record set.
       else {
         if ($this->usesRowPlugin()) {
+          $fibers = [];
           foreach ($set['rows'] as $index => $row) {
-            $this->view->row_index = $index;
-            $set['rows'][$index] = $this->view->rowPlugin->render($row);
+            $fibers[$index] = new \Fiber(function () use ($row, $index) {
+              $this->view->row_index = $index;
+              return $this->view->rowPlugin->render($row);
+            });
+          }
+          $iterations = 0;
+          while (count($fibers) > 0) {
+            foreach ($fibers as $key => $fiber) {
+              if (!$fiber->isStarted()) {
+                $fiber->start();
+              }
+              elseif ($fiber->isSuspended()) {
+                $fiber->resume();
+              }
+              // If the Fiber hasn't terminated by this point, move onto the
+              // next placeholder, we'll resume this Fiber again when we get
+              // back here.
+              if (!$fiber->isTerminated()) {
+                // If we've gone through the placeholders once already, and
+                // they're still not finished, then start to allow code higher
+                // up the stack to get on with something else.
+                if ($iterations) {
+                  $fiber = \Fiber::getCurrent();
+                  if ($fiber !== NULL) {
+                    $fiber->suspend();
+                  }
+                }
+                continue;
+              }
+              $set['rows'][$key] = $fiber->getReturn();
+              unset($fibers[$key]);
+            }
           }
         }
 
@@ -669,84 +701,126 @@ protected function renderFields(array $result) {
       $this->view->row_index = 0;
       $field_ids = array_keys($this->view->field);
 
-      // Only tokens relating to field handlers preceding the one we invoke
-      // ::getRenderTokens() on are returned, so here we need to pick the last
-      // available field handler.
-      $render_tokens_field_id = end($field_ids);
-
       // If all fields have a field::access FALSE there might be no fields, so
       // there is no reason to execute this code.
       if (!empty($field_ids)) {
-        $renderer = $this->getRenderer();
         /** @var \Drupal\views\Plugin\views\cache\CachePluginBase $cache_plugin */
-        $cache_plugin = $this->view->display_handler->getPlugin('cache');
-        $max_age = $cache_plugin->getCacheMaxAge();
 
         /** @var \Drupal\views\ResultRow $row */
+        $fibers = [];
+        $iterations = 0;
         foreach ($result as $index => $row) {
-          $this->view->row_index = $index;
-
-          // Here we implement render caching for result rows. Since we never
-          // build a render array for single rows, given that style templates
-          // need individual field markup to support proper theming, we build
-          // a raw render array containing all field render arrays and cache it.
-          // This allows us to cache the markup of the various children, that is
-          // individual fields, which is then available for style template
-          // preprocess functions, later in the rendering workflow.
-          // @todo Fetch all the available cached row items in one single cache
-          //   get operation, once https://www.drupal.org/node/2453945 is fixed.
-          $data = [
-            '#pre_render' => [[$this, 'elementPreRenderRow']],
-            '#row' => $row,
-            '#cache' => [
-              'keys' => $cache_plugin->getRowCacheKeys($row),
-              'tags' => $cache_plugin->getRowCacheTags($row),
-              'max-age' => $max_age,
-            ],
-            '#cache_properties' => $field_ids,
-          ];
-          $renderer->addCacheableDependency($data, $this->view->storage);
-          // Views may be rendered both inside and outside a render context:
-          // - HTML views are rendered inside a render context: then we want to
-          //   use ::render(), so that attachments and cacheability are bubbled.
-          // - non-HTML views are rendered outside a render context: then we
-          //   want to use ::renderInIsolation(), so that no bubbling happens
-          if ($renderer->hasRenderContext()) {
-            $renderer->render($data);
-          }
-          else {
-            $renderer->renderInIsolation($data);
-          }
-
-          // Extract field output from the render array and post process it.
-          $fields = $this->view->field;
-          $rendered_fields = &$this->rendered_fields[$index];
-          $post_render_tokens = [];
-          foreach ($field_ids as $id) {
-            $rendered_fields[$id] = $data[$id]['#markup'];
-            $tokens = $fields[$id]->postRender($row, $rendered_fields[$id]);
-            if ($tokens) {
-              $post_render_tokens += $tokens;
+          $fibers[$index] = new \Fiber(fn() => $this->renderFieldRow($index, $row, $field_ids));
+        }
+        while (\count($fibers) > 0) {
+          foreach ($fibers as $key => $fiber) {
+            if (!$fiber->isStarted()) {
+              $fiber->start();
             }
-          }
-
-          // Populate row tokens.
-          $this->rowTokens[$index] = $this->view->field[$render_tokens_field_id]->getRenderTokens([]);
-
-          // Replace post-render tokens.
-          if ($post_render_tokens) {
-            $placeholders = array_keys($post_render_tokens);
-            $values = array_values($post_render_tokens);
-            foreach ($this->rendered_fields[$index] as &$rendered_field) {
-              // Placeholders and rendered fields have been processed by the
-              // render system and are therefore safe.
-              $rendered_field = ViewsRenderPipelineMarkup::create(str_replace($placeholders, $values, $rendered_field));
+            elseif ($fiber->isSuspended()) {
+              $fiber->resume();
+            }
+            // If the Fiber hasn't terminated by this point, move onto the next
+            // placeholder, we'll resume this Fiber again when we get back here.
+            if (!$fiber->isTerminated()) {
+              // If we've gone through the placeholders once already, and
+              // they're still not finished, then start to allow code higher up
+              // the stack to get on with something else.
+              if ($iterations) {
+                $fiber = \Fiber::getCurrent();
+                if ($fiber !== NULL) {
+                  $fiber->suspend();
+                }
+              }
+              continue;
             }
+            unset($fibers[$key]);
           }
         }
+        $iterations++;
+      }
+    }
+
+    unset($this->view->row_index);
+  }
+
+  /**
+   * Render an individual field row.
+   *
+   * @param int $index
+   *   The index of this row within the result.
+   * @param Drupal\views\ResultRow $row
+   *   The row to be rendered.
+   * @param array $field_ids
+   *   The field IDs present in this row.
+   */
+  protected function renderFieldRow(int $index, ResultRow $row, array $field_ids): void {
+    // Only tokens relating to field handlers preceding the one we invoke
+    // ::getRenderTokens() on are returned, so here we need to pick the last
+    // available field handler.
+    $render_tokens_field_id = end($field_ids);
+
+    $cache_plugin = $this->view->display_handler->getPlugin('cache');
+    $max_age = $cache_plugin->getCacheMaxAge();
+    $renderer = $this->getRenderer();
+    $this->view->row_index = $index;
+
+    // Here we implement render caching for result rows. Since we never
+    // build a render array for single rows, given that style templates
+    // need individual field markup to support proper theming, we build
+    // a raw render array containing all field render arrays and cache it.
+    // This allows us to cache the markup of the various children, that is
+    // individual fields, which is then available for style template
+    // preprocess functions, later in the rendering workflow.
+    // @todo Fetch all the available cached row items in one single cache
+    //   get operation, once https://www.drupal.org/node/2453945 is fixed.
+    $data = [
+      '#pre_render' => [[$this, 'elementPreRenderRow']],
+      '#row' => $row,
+      '#cache' => [
+        'keys' => $cache_plugin->getRowCacheKeys($row),
+        'tags' => $cache_plugin->getRowCacheTags($row),
+        'max-age' => $max_age,
+      ],
+      '#cache_properties' => $field_ids,
+    ];
+    $renderer->addCacheableDependency($data, $this->view->storage);
+    // Views may be rendered both inside and outside a render context:
+    // - HTML views are rendered inside a render context: then we want to
+    //   use ::render(), so that attachments and cacheability are bubbled.
+    // - non-HTML views are rendered outside a render context: then we
+    //   want to use ::renderInIsolation(), so that no bubbling happens
+    if ($renderer->hasRenderContext()) {
+      $renderer->render($data);
+    }
+    else {
+      $renderer->renderInIsolation($data);
+    }
+
+    // Extract field output from the render array and post process it.
+    $fields = $this->view->field;
+    $rendered_fields = &$this->rendered_fields[$index];
+    $post_render_tokens = [];
+    foreach ($field_ids as $id) {
+      $rendered_fields[$id] = $data[$id]['#markup'];
+      $tokens = $fields[$id]->postRender($row, $rendered_fields[$id]);
+      if ($tokens) {
+        $post_render_tokens += $tokens;
       }
+    }
 
-      unset($this->view->row_index);
+    // Populate row tokens.
+    $this->rowTokens[$index] = $this->view->field[$render_tokens_field_id]->getRenderTokens([]);
+
+    // Replace post-render tokens.
+    if ($post_render_tokens) {
+      $placeholders = array_keys($post_render_tokens);
+      $values = array_values($post_render_tokens);
+      foreach ($this->rendered_fields[$index] as &$rendered_field) {
+        // Placeholders and rendered fields have been processed by the
+        // render system and are therefore safe.
+        $rendered_field = ViewsRenderPipelineMarkup::create(str_replace($placeholders, $values, $rendered_field));
+      }
     }
   }