diff --git a/test/vp9_thread_test.cc b/test/vp9_thread_test.cc
index 41d22dd3a0b1e6488ff9ab6dc6322e9fc69f9f15..4fec46ad4c29eb2498c35d18c3180cb07852ca4f 100644
--- a/test/vp9_thread_test.cc
+++ b/test/vp9_thread_test.cc
@@ -18,7 +18,7 @@
 
 namespace {
 
-class VP9WorkerThreadTest : public ::testing::Test {
+class VP9WorkerThreadTest : public ::testing::TestWithParam<bool> {
  protected:
   virtual ~VP9WorkerThreadTest() {}
   virtual void SetUp() {
@@ -38,7 +38,7 @@ int ThreadHook(void* data, void* return_value) {
   return *reinterpret_cast<int*>(return_value);
 }
 
-TEST_F(VP9WorkerThreadTest, HookSuccess) {
+TEST_P(VP9WorkerThreadTest, HookSuccess) {
   EXPECT_TRUE(vp9_worker_sync(&worker_));  // should be a no-op.
 
   for (int i = 0; i < 2; ++i) {
@@ -50,7 +50,12 @@ TEST_F(VP9WorkerThreadTest, HookSuccess) {
     worker_.data1 = &hook_data;
     worker_.data2 = &return_value;
 
-    vp9_worker_launch(&worker_);
+    const bool synchronous = GetParam();
+    if (synchronous) {
+      vp9_worker_execute(&worker_);
+    } else {
+      vp9_worker_launch(&worker_);
+    }
     EXPECT_TRUE(vp9_worker_sync(&worker_));
     EXPECT_FALSE(worker_.had_error);
     EXPECT_EQ(5, hook_data);
@@ -59,7 +64,7 @@ TEST_F(VP9WorkerThreadTest, HookSuccess) {
   }
 }
 
-TEST_F(VP9WorkerThreadTest, HookFailure) {
+TEST_P(VP9WorkerThreadTest, HookFailure) {
   EXPECT_TRUE(vp9_worker_reset(&worker_));
 
   int hook_data = 0;
@@ -68,7 +73,12 @@ TEST_F(VP9WorkerThreadTest, HookFailure) {
   worker_.data1 = &hook_data;
   worker_.data2 = &return_value;
 
-  vp9_worker_launch(&worker_);
+  const bool synchronous = GetParam();
+  if (synchronous) {
+    vp9_worker_execute(&worker_);
+  } else {
+    vp9_worker_launch(&worker_);
+  }
   EXPECT_FALSE(vp9_worker_sync(&worker_));
   EXPECT_TRUE(worker_.had_error);
 
@@ -106,4 +116,6 @@ TEST(VP9DecodeMTTest, MTDecode) {
   EXPECT_STREQ("b35a1b707b28e82be025d960aba039bc", md5.Get());
 }
 
+INSTANTIATE_TEST_CASE_P(Synchronous, VP9WorkerThreadTest, ::testing::Bool());
+
 }  // namespace
diff --git a/vp9/decoder/vp9_decodframe.c b/vp9/decoder/vp9_decodframe.c
index b79ff554d820e7d36aaa158402cda8033ea677c6..46bfbe25da12107c7a6be99d7e32fd98bd67d47d 100644
--- a/vp9/decoder/vp9_decodframe.c
+++ b/vp9/decoder/vp9_decodframe.c
@@ -583,14 +583,12 @@ static void decode_tile(VP9D_COMP *pbi, vp9_reader *r, int tile_col) {
   YV12_BUFFER_CONFIG *const fb = &cm->yv12_fb[cm->new_fb_idx];
 
   if (pbi->do_loopfilter_inline) {
-    if (num_threads > 1) {
-      LFWorkerData *const lf_data = (LFWorkerData*)pbi->lf_worker.data1;
-      lf_data->frame_buffer = fb;
-      lf_data->cm = cm;
-      lf_data->xd = pbi->mb;
-      lf_data->stop = 0;
-      lf_data->y_only = 0;
-    }
+    LFWorkerData *const lf_data = (LFWorkerData*)pbi->lf_worker.data1;
+    lf_data->frame_buffer = fb;
+    lf_data->cm = cm;
+    lf_data->xd = pbi->mb;
+    lf_data->stop = 0;
+    lf_data->y_only = 0;
     vp9_loop_filter_frame_init(cm, cm->lf.filter_level);
   }
 
@@ -604,39 +602,33 @@ static void decode_tile(VP9D_COMP *pbi, vp9_reader *r, int tile_col) {
       decode_modes_sb(pbi, tile_col, mi_row, mi_col, r, BLOCK_64X64, 0);
 
     if (pbi->do_loopfilter_inline) {
-      // delay the loopfilter by 1 macroblock row.
       const int lf_start = mi_row - MI_BLOCK_SIZE;
-      if (lf_start < 0) continue;
+      LFWorkerData *const lf_data = (LFWorkerData*)pbi->lf_worker.data1;
 
-      if (num_threads > 1) {
-        LFWorkerData *const lf_data = (LFWorkerData*)pbi->lf_worker.data1;
+      // delay the loopfilter by 1 macroblock row.
+      if (lf_start < 0) continue;
 
-        // decoding has completed: finish up the loop filter in this thread.
-        if (mi_row + MI_BLOCK_SIZE >= cm->cur_tile_mi_row_end) continue;
+      // decoding has completed: finish up the loop filter in this thread.
+      if (mi_row + MI_BLOCK_SIZE >= cm->cur_tile_mi_row_end) continue;
 
-        vp9_worker_sync(&pbi->lf_worker);
-        lf_data->start = lf_start;
-        lf_data->stop = mi_row;
-        pbi->lf_worker.hook = vp9_loop_filter_worker;
+      vp9_worker_sync(&pbi->lf_worker);
+      lf_data->start = lf_start;
+      lf_data->stop = mi_row;
+      if (num_threads > 1) {
         vp9_worker_launch(&pbi->lf_worker);
       } else {
-        vp9_loop_filter_rows(fb, cm, &pbi->mb, lf_start, mi_row, 0);
+        vp9_worker_execute(&pbi->lf_worker);
       }
     }
   }
 
   if (pbi->do_loopfilter_inline) {
-    int lf_start;
-    if (num_threads > 1) {
-      LFWorkerData *const lf_data = (LFWorkerData*)pbi->lf_worker.data1;
+    LFWorkerData *const lf_data = (LFWorkerData*)pbi->lf_worker.data1;
 
-      vp9_worker_sync(&pbi->lf_worker);
-      lf_start = lf_data->stop;
-    } else {
-      lf_start = mi_row - MI_BLOCK_SIZE;
-    }
-    vp9_loop_filter_rows(fb, cm, &pbi->mb,
-                         lf_start, cm->mi_rows, 0);
+    vp9_worker_sync(&pbi->lf_worker);
+    lf_data->start = lf_data->stop;
+    lf_data->stop = cm->mi_rows;
+    vp9_worker_execute(&pbi->lf_worker);
   }
 }
 
diff --git a/vp9/decoder/vp9_onyxd_if.c b/vp9/decoder/vp9_onyxd_if.c
index c1fbee31236cf70e463ad7f54469c8bdd3ab689b..243dbef214891402b7033036d4e6444b8bc8feb9 100644
--- a/vp9/decoder/vp9_onyxd_if.c
+++ b/vp9/decoder/vp9_onyxd_if.c
@@ -141,14 +141,13 @@ VP9D_PTR vp9_create_decompressor(VP9D_CONFIG *oxcf) {
   cm->error.setjmp = 0;
   pbi->decoded_key_frame = 0;
 
-  if (pbi->oxcf.max_threads > 1) {
-    vp9_worker_init(&pbi->lf_worker);
-    pbi->lf_worker.data1 = vpx_malloc(sizeof(LFWorkerData));
-    pbi->lf_worker.hook = (VP9WorkerHook)vp9_loop_filter_worker;
-    if (pbi->lf_worker.data1 == NULL || !vp9_worker_reset(&pbi->lf_worker)) {
-      vp9_remove_decompressor(pbi);
-      return NULL;
-    }
+  vp9_worker_init(&pbi->lf_worker);
+  pbi->lf_worker.data1 = vpx_malloc(sizeof(LFWorkerData));
+  pbi->lf_worker.hook = (VP9WorkerHook)vp9_loop_filter_worker;
+  if (pbi->lf_worker.data1 == NULL ||
+      (pbi->oxcf.max_threads > 1 && !vp9_worker_reset(&pbi->lf_worker))) {
+    vp9_remove_decompressor(pbi);
+    return NULL;
   }
 
   return pbi;
diff --git a/vp9/decoder/vp9_thread.c b/vp9/decoder/vp9_thread.c
index 5442ddfa190ef4eb66ce8d31afa732eca6525372..d953e72b333aa724e7174e9f3b564e41b9e6501b 100644
--- a/vp9/decoder/vp9_thread.c
+++ b/vp9/decoder/vp9_thread.c
@@ -145,9 +145,7 @@ static THREADFN thread_loop(void *ptr) {    // thread loop
       pthread_cond_wait(&worker->condition_, &worker->mutex_);
     }
     if (worker->status_ == WORK) {
-      if (worker->hook) {
-        worker->had_error |= !worker->hook(worker->data1, worker->data2);
-      }
+      vp9_worker_execute(worker);
       worker->status_ = OK;
     } else if (worker->status_ == NOT_OK) {   // finish the worker
       done = 1;
@@ -178,7 +176,7 @@ static void change_state(VP9Worker* const worker,
   pthread_mutex_unlock(&worker->mutex_);
 }
 
-#endif
+#endif  // CONFIG_MULTITHREAD
 
 //------------------------------------------------------------------------------
 
@@ -218,12 +216,17 @@ int vp9_worker_reset(VP9Worker* const worker) {
   return ok;
 }
 
+void vp9_worker_execute(VP9Worker* const worker) {
+  if (worker->hook != NULL) {
+    worker->had_error |= !worker->hook(worker->data1, worker->data2);
+  }
+}
+
 void vp9_worker_launch(VP9Worker* const worker) {
 #if CONFIG_MULTITHREAD
   change_state(worker, WORK);
 #else
-  if (worker->hook)
-    worker->had_error |= !worker->hook(worker->data1, worker->data2);
+  vp9_worker_execute(worker);
 #endif
 }
 
diff --git a/vp9/decoder/vp9_thread.h b/vp9/decoder/vp9_thread.h
index e5e6f606b87ec7b8c867faf0d55d2e9a2b09a3f1..a624f3c2ab78fe3ea83c08f6132fe2d24444dcef 100644
--- a/vp9/decoder/vp9_thread.h
+++ b/vp9/decoder/vp9_thread.h
@@ -80,6 +80,11 @@ int vp9_worker_sync(VP9Worker* const worker);
 // hook/data1/data2 can be changed at any time before calling this function,
 // but not be changed afterward until the next call to vp9_worker_sync().
 void vp9_worker_launch(VP9Worker* const worker);
+// This function is similar to vp9_worker_launch() except that it calls the
+// hook directly instead of using a thread. Convenient to bypass the thread
+// mechanism while still using the VP9Worker structs. vp9_worker_sync() must
+// still be called afterward (for error reporting).
+void vp9_worker_execute(VP9Worker* const worker);
 // Kill the thread and terminate the object. To use the object again, one
 // must call vp9_worker_reset() again.
 void vp9_worker_end(VP9Worker* const worker);