diff --git a/include/mbgl/actor/scheduler.hpp b/include/mbgl/actor/scheduler.hpp index 111532879..04cbc389e 100644 --- a/include/mbgl/actor/scheduler.hpp +++ b/include/mbgl/actor/scheduler.hpp @@ -69,8 +69,21 @@ public: // Get the scheduler for asynchronous tasks. This method // will lazily initialize a shared worker pool when ran // from the first time. + // The scheduled tasks might run in parallel on different + // threads. + // TODO : Rename to GetPool() static std::shared_ptr GetBackground(); + // Get the *sequenced* scheduler for asynchronous tasks. + // Unlike the method above, the returned scheduler + // (once stored) represents a single thread, thus each + // newly scheduled task is guarantied to run after the + // previously scheduled one. + // + // Sequenced scheduler can be used for running tasks + // on the same thread-unsafe object. + static std::shared_ptr GetSequenced(); + protected: template void scheduleAndReplyValue(const TaskFn& task, diff --git a/src/mbgl/actor/scheduler.cpp b/src/mbgl/actor/scheduler.cpp index 81e259fe1..0e051d027 100644 --- a/src/mbgl/actor/scheduler.cpp +++ b/src/mbgl/actor/scheduler.cpp @@ -41,4 +41,31 @@ std::shared_ptr Scheduler::GetBackground() { return scheduler; } +// static +std::shared_ptr Scheduler::GetSequenced() { + const std::size_t kSchedulersCount = 10; + static std::vector> weaks(kSchedulersCount); + static std::mutex mtx; + static std::size_t lastUsedIndex = 0u; + + std::lock_guard lock(mtx); + + if (++lastUsedIndex == kSchedulersCount) lastUsedIndex = 0u; + + std::shared_ptr result; + for (std::size_t i = 0; i < kSchedulersCount; ++i) { + auto& weak = weaks[i]; + if (auto scheduler = weak.lock()) { + if (lastUsedIndex == i) result = scheduler; + continue; + } + result = std::make_shared(); + weak = result; + lastUsedIndex = i; + break; + } + + return result; +} + } //namespace mbgl diff --git a/test/util/async_task.test.cpp b/test/util/async_task.test.cpp index 214e490fd..8c79c5140 100644 --- a/test/util/async_task.test.cpp +++ b/test/util/async_task.test.cpp @@ -169,4 +169,47 @@ TEST(AsyncTask, scheduleAndReplyValue) { auto sheduler = Scheduler::GetBackground(); sheduler->scheduleAndReplyValue(runInBackground, onResult); loop.run(); +} + +TEST(AsyncTask, SequencedScheduler) { + RunLoop loop; + std::thread::id caller_id = std::this_thread::get_id(); + std::thread::id bg_id; + int count = 0; + + auto first = [caller_id, &bg_id, &count]() { + EXPECT_EQ(0, count); + bg_id = std::this_thread::get_id(); + EXPECT_NE(caller_id, bg_id); + count++; + }; + auto second = [&bg_id, &count]() { + EXPECT_EQ(1, count); + EXPECT_EQ(bg_id, std::this_thread::get_id()); + count++; + }; + auto third = [&bg_id, &count, &loop]() { + EXPECT_EQ(2, count); + EXPECT_EQ(bg_id, std::this_thread::get_id()); + loop.stop(); + }; + + auto sheduler = Scheduler::GetSequenced(); + + sheduler->schedule(first); + sheduler->schedule(second); + sheduler->schedule(third); + loop.run(); +} + +TEST(AsyncTask, MultipleSequencedSchedulers) { + std::vector> shedulers; + + for (int i = 0; i < 10; ++i) { + auto scheduler = Scheduler::GetSequenced(); + EXPECT_TRUE(std::none_of( + shedulers.begin(), shedulers.end(), [&scheduler](const auto &item) { return item == scheduler; })); + shedulers.emplace_back(std::move(scheduler)); + } + EXPECT_EQ(shedulers.front(), Scheduler::GetSequenced()); } \ No newline at end of file