cesium-native 0.43.0
Loading...
Searching...
No Matches
QueuedScheduler.h
1#pragma once
2
3#include "ImmediateScheduler.h"
4#include "cesium-async++.h"
5
6#include <atomic>
7
8namespace CesiumAsync {
9// Begin omitting doxygen warnings for Impl namespace
11namespace CesiumImpl {
12
13class QueuedScheduler {
14public:
15 QueuedScheduler();
16 ~QueuedScheduler();
17
18 void schedule(async::task_run_handle t);
19 void dispatchQueuedContinuations();
20 bool dispatchZeroOrOneContinuation();
21
22 template <typename T> T dispatchUntilTaskCompletes(async::task<T>&& task) {
23 // Set up a continuation to unblock the blocking dispatch when this task
24 // completes.
25 //
26 // We use the `isDone` flag as the loop termination condition to
27 // avoid a race condition that can lead to a deadlock. If we used
28 // `unblockTask.ready()` as the termination condition instead, then it's
29 // possible for events to happen as follows:
30 //
31 // 1. The original `task` completes in a worker thread and the `unblockTask`
32 // continuation is invoked immediately in the same thread.
33 // 2. The unblockTask continuation calls `unblock`, which terminates the
34 // `wait` on the condition variable in the main thread.
35 // 3. The main thread resumes and the while loop in this function spins back
36 // around and evaluates `unblockTask.ready()`. This returns false because
37 // the unblockTask continuation has not actually finished running in the
38 // worker thread yet. The main thread starts waiting on the condition
39 // variable again.
40 // 4. The `unblockTask` continuation finally finishes, making
41 // `unblockTask.ready()` return true, but it's too late. The main thread is
42 // already waiting on the condition variable.
43 //
44 // By setting the atomic `isDone` flag before calling `unblock`, we ensure
45 // that the loop termination condition is satisfied before the main thread
46 // is awoken, avoiding the potential deadlock.
47
48 std::atomic<bool> isDone = false;
49 async::task<T> unblockTask = task.then(
50 async::inline_scheduler(),
51 [this, &isDone](async::task<T>&& task) {
52 isDone = true;
53 this->unblock();
54 return task.get();
55 });
56
57 while (!isDone) {
58 this->dispatchInternal(true);
59 }
60
61 return std::move(unblockTask).get();
62 }
63
64 template <typename T>
65 T dispatchUntilTaskCompletes(const async::shared_task<T>& task) {
66 // Set up a continuation to unblock the blocking dispatch when this task
67 // completes. This case is simpler than the one above because a SharedFuture
68 // supports multiple continuations. We can use readiness of the _original_
69 // task to terminate the loop while unblocking in a separate continuation
70 // guaranteed to run only after that termination condition is satisfied.
71 async::task<void> unblockTask = task.then(
72 async::inline_scheduler(),
73 [this](const async::shared_task<T>&) { this->unblock(); });
74
75 while (!task.ready()) {
76 this->dispatchInternal(true);
77 }
78
79 return task.get();
80 }
81
82 ImmediateScheduler<QueuedScheduler> immediate{this};
83
84private:
85 bool dispatchInternal(bool blockIfNoTasks);
86 void unblock();
87
88 struct Impl;
89 std::unique_ptr<Impl> _pImpl;
90};
92// End omitting doxygen warnings for Impl namespace
93
94} // namespace CesiumImpl
95} // namespace CesiumAsync
Classes that support asynchronous operations.