Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
ThreadPool.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#include "Stroika/Foundation/StroikaPreComp.h"
5
9#include "Stroika/Foundation/Debug/Main.h"
12
13#include "Common.h"
14#include "Sleep.h"
15#include "TimeOutException.h"
16
17#include "ThreadPool.h"
18
19using namespace Stroika::Foundation;
22using namespace Stroika::Foundation::Execution;
23
24// Comment this in to turn on aggressive noisy DbgTrace in this module
25//#define USE_NOISY_TRACE_IN_THIS_MODULE_ 1
26
27namespace {
28 constexpr bool kEmitDbgTraceOnThreadPoolEntryExceptions_ = qStroika_Foundation_Debug_AssertionsChecked;
29}
30
31class ThreadPool::MyRunnable_ {
32public:
33 MyRunnable_ (ThreadPool& threadPool)
34 : fThreadPool{threadPool}
35 {
36 }
37
38public:
39 tuple<TaskType, Time::TimePointSeconds, optional<String>> GetCurTaskInfo () const
40 {
41 // assume caller holds lock
42 return make_tuple (fCurTask, fCurTaskStartedAt, fCurName); // note curTask can be null, in which case these other things are meaningless
43 }
44
45public:
46 nonvirtual void Run ()
47 {
48 // For NOW - allow ThreadAbort to just kill this thread. In the future - we may want to implement some sort of restartability
49
50 // Keep grabbing new tasks, and running them
51 while (true) {
52 {
54 [[maybe_unused]] lock_guard critSec{fThreadPool.fCriticalSection_};
55 Assert (fCurTask == nullptr);
56 }
57 // Subtle point, but we must copy directly into fCurTask (WaitForNextTask_ call) so its filled in under lock
58 // while being moved so task moves from pending to in-use without ever temporarily disappearing from known tasks lists
59 fThreadPool.WaitForNextTask_ (&fCurTask, &fCurName); // This will block INDEFINITELY until ThreadAbort throws out or we have a new task to run
61 [[maybe_unused]] lock_guard critSec{fThreadPool.fCriticalSection_};
62 Assert (fCurTask != nullptr);
63 }
64 }
65 [[maybe_unused]] auto&& cleanup = Finally ([this] () noexcept {
66 Time::TimePointSeconds taskStartedAt;
67 {
68 [[maybe_unused]] lock_guard critSec{fThreadPool.fCriticalSection_};
69 fCurTask = nullptr;
70 taskStartedAt = fCurTaskStartedAt;
71 }
72 if (fThreadPool.fCollectStatistics_) {
73 [[maybe_unused]] lock_guard critSec{fThreadPool.fCriticalSection_};
74 ++fThreadPool.fCollectedTaskStats_.fNumberOfTasksCompleted;
75 ++fThreadPool.fCollectedTaskStats_.fNumberOfTasksReporting;
76 fThreadPool.fCollectedTaskStats_.fTotalTimeConsumed += Time::GetTickCount () - taskStartedAt;
77 }
78 });
79 try {
80 // Use lock to access fCurTask, but don't hold the lock during run, so others can call getcurrenttask
81 ThreadPool::TaskType task2Run;
82 {
83 [[maybe_unused]] lock_guard critSec{fThreadPool.fCriticalSection_};
84 task2Run = fCurTask;
85 fCurTaskStartedAt = Time::GetTickCount ();
86 }
87 task2Run ();
88 }
89 catch (const Thread::AbortException&) {
90 throw; // cancel this thread
91 }
92 catch (...) {
93 // other exceptions WARNING WITH DEBUG MESSAGE - but otherwise - EAT/IGNORE
94 if constexpr (kEmitDbgTraceOnThreadPoolEntryExceptions_ and qStroika_Foundation_Debug_DefaultTracingOn) {
95 DbgTrace ("in threadpool, ignoring exception running task: {}"_f, current_exception ());
96 }
97 }
98 }
99 }
100
101public:
102 ThreadPool& fThreadPool;
103 // fThreadPool.fCriticalSection_ protect access to fCurTask/fCurTaskStartedAt/fCurName - very short duration
104 ThreadPool::TaskType fCurTask;
105 Time::TimePointSeconds fCurTaskStartedAt{0s}; // meaningless if fCurTask==nullptr
106 optional<Characters::String> fCurName; // ""
107};
108
109/*
110 ********************************************************************************
111 ************************ Execution::ThreadPool::Statistics *********************
112 ********************************************************************************
113 */
115{
116 StringBuilder sb;
117 sb << "{"sv;
118 sb << "NumberOfTasksAdded: "sv << fNumberOfTasksAdded;
119 sb << ", NumberOfTasksCompleted: "sv << fNumberOfTasksCompleted;
120 sb << ", TotalTimeConsumed: "sv << fTotalTimeConsumed;
121 sb << "}"sv;
122 return sb;
123}
124
125/*
126 ********************************************************************************
127 ****************************** Execution::ThreadPool ***************************
128 ********************************************************************************
129 */
130ThreadPool::ThreadPool (const Options& options)
131 : fCollectStatistics_{options.fCollectStatistics}
132 , fDefaultQMax_{options.fQMax}
133 , fThreadPoolName_{options.fThreadPoolName}
134{
135 Require (Debug::AppearsDuringMainLifetime ());
136 SetPoolSize (options.fThreadCount);
137}
138
140{
141 Require (Debug::AppearsDuringMainLifetime ());
142 AbortAndWaitForDone_ ();
143}
144
145unsigned int ThreadPool::GetPoolSize () const
146{
147 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
148 return static_cast<unsigned int> (fThreads_.size ());
149}
150
151void ThreadPool::SetPoolSize (unsigned int poolSize)
152{
153 Debug::TraceContextBumper ctx{"ThreadPool::SetPoolSize", "newPoolSize={}, *this={}"_f, poolSize, ToString ()};
154 Require (not fAborted_);
155 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
156 DbgTrace ("fThreads_.size ()={}"_f, fThreads_.size ());
157 while (poolSize > fThreads_.size ()) {
158 fThreads_.Add (mkThread_ ());
159 }
160
161 // Still quite weak implementation of REMOVAL
162 while (poolSize < fThreads_.size ()) {
163 // iterate over threads if any not busy, remove that them first
164 bool anyFoundToKill = false;
165 for (Iterator<TPInfo_> i = fThreads_.begin (); i != fThreads_.end (); ++i) {
166 TaskType ct{i->fRunnable->fCurTask};
167 if (ct == nullptr) {
168 // since we have fCriticalSection_ - we can safely remove this thread
169 fThreads_.Remove (i);
170 anyFoundToKill = true;
171 break;
172 }
173 }
174 if (not anyFoundToKill) {
175 // @todo - fix this better/eventually - either throw or wait...
176 DbgTrace ("Failed to lower the loop size - cuz all threads busy - giving up"_f);
177 return;
178 }
179 }
180}
181
182auto ThreadPool::AddTask (const TaskType& task, QMax qmax, const optional<Characters::String>& name) -> TaskType
183{
184 // also INTENTIONALLY dont hold lock long enuf to make this work 100% reliably cuz these magic numbers dont need to be precise, just approximate
185 // @todo rewrite this with condition variables, so more efficient and wakes up/times out appropriately...
186 Time::TimePointSeconds timeoutAt = Time::GetTickCount () + qmax.fAddBlockTimeout;
187#if qStroika_Foundation_Debug_AssertionsChecked
188 bool blockedAtLeastOnce = false;
189#endif
190 while (true) {
191 if (GetPendingTasksCount () >= qmax.fLength) [[unlikely]] {
192#if qStroika_Foundation_Debug_AssertionsChecked
193 if (not blockedAtLeastOnce) {
194 DbgTrace ("Blocking inside ThreadPool::AddTask due to excessive pending task count"_f);
195 blockedAtLeastOnce = true;
196 }
197#endif
198 ThrowTimeoutExceptionAfter (timeoutAt);
199 Execution::Sleep (500ms); // @todo fix and use condition variable - but good news is can only happen if fAddBlockTimeout != 0s
200 }
201 else {
202 return AddTask_ (task, name);
203 }
204 }
205}
206
207auto ThreadPool::AddTask_ (const TaskType& task, const optional<Characters::String>& name) -> TaskType
208{
209#if USE_NOISY_TRACE_IN_THIS_MODULE_
210 Debug::TraceContextBumper ctx{"ThreadPool::AddTask_"};
211#endif
212 Require (not fAborted_);
213 {
214 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
215 fPendingTasks_.push_back (PendingTaskInfo_{.fTask = task, .fName = name});
216#if USE_NOISY_TRACE_IN_THIS_MODULE_
217 DbgTrace ("fPendingTasks.size () now = {}"_f, (int)fPendingTasks_.size ());
218#endif
219 ++fCollectedTaskStats_.fNumberOfTasksAdded;
220 }
221
222 // Notify any waiting threads to wakeup and claim the next task
223 fTasksMaybeAdded_.Set ();
224 // this would be a race - if aborting and adding tasks at the same time
225 Require (not fAborted_);
226 return task;
227}
228
230{
231 Debug::TraceContextBumper ctx{"ThreadPool::AbortTask"};
232 {
233 // First see if its in the Q
234 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
235 for (auto i = fPendingTasks_.begin (); i != fPendingTasks_.end (); ++i) {
236 if (i->fTask == task) {
237 fPendingTasks_.erase (i);
238 return;
239 }
240 }
241 }
242
243 // If we got here - its NOT in the task Q, so maybe its already running.
244 //
245 //
246
247 // TODO:
248 // We walk the list of existing threads and ask each one if its (indirected - running task) is the given one and abort that task.
249 // But that requires we can RESTART an ABORTED thread (or that we remove it from the list - maybe thats better). THat COULD be OK
250 // actually since it involves on API changes and makes sense. The only slight issue is a peformace one but probably for something
251 // quite rare.
252 //
253 // Anyhow SB OK for now to just not allow aborting a task which has already started....
254 Thread::Ptr thread2Kill;
255 {
256 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
257 for (Iterator<TPInfo_> i = fThreads_.begin (); i != fThreads_.end (); ++i) {
258 TaskType ct{i->fRunnable->fCurTask};
259 if (task == ct) {
260 thread2Kill = i->fThread;
261 fThreads_.Update (i, mkThread_ ());
262 break;
263 }
264 }
265 }
266 if (thread2Kill != nullptr) {
267 thread2Kill.AbortAndWaitForDone (timeout);
268 }
269}
270
272{
273 Debug::TraceContextBumper ctx{"ThreadPool::AbortTasks"};
274 auto tps = GetPoolSize ();
275 {
276 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
277 fPendingTasks_.clear ();
278 for (const TPInfo_& ti : fThreads_) {
279 ti.fThread.Abort ();
280 }
281 }
282 {
283 // @todo maybe fix unsafe - waiting here holding the critsec lock - seems deadlock waiting to happen - LGP 2023-11-05
284 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
285 for (const TPInfo_& ti : fThreads_) {
286 // @todo fix wrong timeout value here
287 ti.fThread.AbortAndWaitForDone (timeout);
288 }
289 fThreads_.RemoveAll ();
290 }
291 // hack - not a good design or impl!!! - waste to recreate if not needed!
292 SetPoolSize (tps);
293}
294
295bool ThreadPool::IsPresent (const TaskType& task) const
296{
297 Require (task != nullptr);
298 {
299 // First see if its in the Q
300 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
301 for (auto i = fPendingTasks_.begin (); i != fPendingTasks_.end (); ++i) {
302 if (i->fTask == task) {
303 return true;
304 }
305 }
306 // then check if running
307 for (auto i = fThreads_.begin (); i != fThreads_.end (); ++i) {
308 TaskType rTask{i->fRunnable->fCurTask};
309 if (task == rTask) {
310 return true;
311 }
312 }
313 }
314 return false;
315}
316
317bool ThreadPool::IsRunning (const TaskType& task) const
318{
319 Require (task != nullptr);
320 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
321 for (auto i = fThreads_.begin (); i != fThreads_.end (); ++i) {
322 if (task == i->fRunnable->fCurTask) {
323 return true;
324 }
325 }
326 return false;
327}
328
330{
331 Debug::TraceContextBumper ctx{"ThreadPool::WaitForTask"};
332 // Inefficient / VERY SLOPPY impl - @todo fix use WaitableEvent or condition variables...
334 TimePointSeconds timeoutAt = timeout + Time::GetTickCount ();
335 while (true) {
336 if (not IsPresent (task)) {
337 return;
338 }
339 Time::DurationSeconds remaining = timeoutAt - Time::GetTickCount ();
340 ThrowTimeoutExceptionAfter (timeoutAt);
341 Execution::Sleep (min<Time::DurationSeconds> (remaining, 1.0s));
342 }
343}
344
345auto ThreadPool::GetTasks () const -> Collection<TaskInfo>
346{
348 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
349 for (const auto& ti : fPendingTasks_) {
350 result.Add (TaskInfo{.fTask = ti.fTask, .fName = ti.fName});
351 }
352 for (auto i = fThreads_.begin (); i != fThreads_.end (); ++i) {
353 tuple<TaskType, Time::TimePointSeconds, optional<String>> curTaskInfo = i->fRunnable->GetCurTaskInfo ();
354 if (get<TaskType> (curTaskInfo) != nullptr) {
355 result.Add (TaskInfo{
356 .fTask = get<TaskType> (curTaskInfo),
357 .fName = get<optional<String>> (curTaskInfo),
358 .fRunningSince = get<Time::TimePointSeconds> (curTaskInfo),
359 });
360 }
361 }
362 return result;
363}
364
366{
368 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
369 for (auto i = fThreads_.begin (); i != fThreads_.end (); ++i) {
370 TaskType task{i->fRunnable->fCurTask};
371 if (task != nullptr) {
372 result.Add (task);
373 }
374 }
375 return result;
376}
377
379{
380 // First see if its in the Q
381 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
382 size_t count = fPendingTasks_.size ();
383 for (auto i = fThreads_.begin (); i != fThreads_.end (); ++i) {
384 AssertNotNull (i->fRunnable);
385 if (i->fRunnable->fCurTask != nullptr) {
386 ++count;
387 }
388 }
389 return count;
390}
391
393{
395 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
396 for (const auto& i : fPendingTasks_) {
397 result.Add (i.fTask);
398 }
399 return result;
400}
401
403{
404 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
405 return fPendingTasks_.size ();
406}
407
409{
410#if USE_NOISY_TRACE_IN_THIS_MODULE_
412 "ThreadPool::WaitForTasksDoneUntil", "*this={}, tasks={}, timeoutAt={}"_f, ToString (), ToString (tasks), timeoutAt)};
413#endif
415 for (const auto& task : tasks) {
416 auto now = Time::GetTickCount ();
417 ThrowTimeoutExceptionAfter (timeoutAt);
418 WaitForTask (task, timeoutAt - now);
419 }
420}
421
423{
424#if USE_NOISY_TRACE_IN_THIS_MODULE_
425 Debug::TraceContextBumper ctx{Stroika_Foundation_Debug_OptionalizeTraceArgs ("ThreadPool::WaitForTasksDoneUntil",
426 "*this={}, timeoutAt={}"_f, ToString (), timeoutAt)};
427#endif
429 // @todo - use WaitablEvent - this is a horrible implementation
430 while (GetTasksCount () != 0) {
431 ThrowTimeoutExceptionAfter (timeoutAt);
432 Execution::Sleep (100ms);
433 }
434}
435
437{
438 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
439 Require (fCollectStatistics_);
440 fCollectedTaskStats_ = {};
441}
442
444{
445 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
446 Require (fCollectStatistics_);
447 return fCollectedTaskStats_;
448}
449
450void ThreadPool::Abort_ () noexcept
451{
452 Thread::SuppressInterruptionInContext suppressCtx; // must cleanly shut down each of our sub-threads - even if our thread is aborting... don't be half-way aborted
453 Debug::TraceContextBumper ctx{Stroika_Foundation_Debug_OptionalizeTraceArgs ("ThreadPool::Abort_", "*this={}"_f, ToString ())};
454 Debug::TimingTrace tt{1.0s};
455 fAborted_ = true; // No race, because fAborted never 'unset'
456 // no need to set fTasksMaybeAdded_, since aborting each thread should be sufficient
457 {
458 // Clear the task Q and then abort each thread
459 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
460 fPendingTasks_.clear ();
461 for (const TPInfo_& ti : fThreads_) {
462 ti.fThread.Abort ();
463 }
464 }
465}
466
467void ThreadPool::AbortAndWaitForDone_ () noexcept
468{
469 Thread::SuppressInterruptionInContext suppressCtx; // cuz we must shutdown owned threads
470#if USE_NOISY_TRACE_IN_THIS_MODULE_
471 Debug::TraceContextBumper ctx{Stroika_Foundation_Debug_OptionalizeTraceArgs ("ThreadPool::AbortAndWaitForDone_", "*this={}"_f, ToString ())};
472 Debug::TimingTrace tt{1.0s};
473#endif
474 try {
475 Abort_ (); // to get the rest of the threadpool abort stuff triggered - flag saying aborting
476 Collection<Thread::Ptr> threadsToShutdown;
477 {
478 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
479 fThreads_.Apply ([&] (const TPInfo_& i) { threadsToShutdown.Add (i.fThread); });
480 }
481 Thread::AbortAndWaitForDone (threadsToShutdown);
482 {
483 [[maybe_unused]] lock_guard critSec{fCriticalSection_}; // they should all be shutdown now
484 fThreads_.Apply ([&] (const TPInfo_& i) { Assert (i.fThread.IsDone ()); });
485 fThreads_.RemoveAll ();
486 }
487 }
488 catch (...) {
489 DbgTrace ("ThreadPool::AbortAndWaitForDone_: serious bug/exception"_f);
490 AssertNotReached (); // this should never happen due to the SuppressInterruptionInContext...
491 }
492 Ensure (fThreads_.empty ());
493}
494
496{
497 StringBuilder sb;
498 sb << "{"sv;
499 {
500 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
501 if (fThreadPoolName_) {
502 sb << "pool-name: '{}'"_f(*fThreadPoolName_);
503 }
504 }
505 if (fThreadPoolName_) {
506 sb << ", "sv;
507 }
508 sb << "pending-task-count: {}"_f(GetPendingTasksCount ());
509 sb << ", running-task-count: {}"_f(GetRunningTasks ().size ());
510 {
511 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
512 sb << ", pool-thread-count: {}"_f(fThreads_.size ());
513 }
514 sb << "}"sv;
515 return sb;
516}
517
518// THIS is called NOT from 'this' - but from the context of an OWNED thread of the pool
519void ThreadPool::WaitForNextTask_ (TaskType* result, optional<Characters::String>* resultName)
520{
521 RequireNotNull (result);
522 RequireNotNull (resultName);
523 Require (*result == nullptr);
524 while (true) {
525 if (fAborted_) [[unlikely]] {
526 Throw (Thread::AbortException::kThe);
527 }
528
529 {
530 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
531 if (not fPendingTasks_.empty ()) {
532 *result = fPendingTasks_.front ().fTask;
533 *resultName = fPendingTasks_.front ().fName;
534 fPendingTasks_.pop_front ();
535 DbgTrace ("ThreadPool::WaitForNextTask_ () pulled a new task from 'pending-tasks' to run on this thread, leaving pending-task-list-size = {}"_f,
536 fPendingTasks_.size ());
537 Ensure (*result != nullptr);
538 return;
539 }
540 }
541
542 // Prevent spinwaiting... This event is SET when any new item arrives
543 //DbgTrace ("ThreadPool::WaitForNextTask_ () - about to wait for added tasks");
544 fTasksMaybeAdded_.WaitAndReset ();
545 //DbgTrace ("ThreadPool::WaitForNextTask_ () - completed wait for added tasks");
546 }
547}
548
549ThreadPool::TPInfo_ ThreadPool::mkThread_ ()
550{
551 shared_ptr<MyRunnable_> r{make_shared<ThreadPool::MyRunnable_> (*this)};
552 StringBuilder entryName = "TPE #{}"_f(fNextThreadEntryNumber_++); // make name so short cuz unix only shows first 15 chars - http://man7.org/linux/man-pages/man3/pthread_setname_np.3.html
553 entryName += " {"sv + fThreadPoolName_.value_or ("anonymous-thread-pool"sv) + "}"sv;
554 Thread::Ptr t = Thread::New ([r] () { r->Run (); }, Thread::eAutoStart, entryName); // race condition for updating this number, but who cares - its purely cosmetic...
555 return TPInfo_{t, r};
556}
#define AssertNotNull(p)
Definition Assertions.h:333
#define qStroika_Foundation_Debug_AssertionsChecked
The qStroika_Foundation_Debug_AssertionsChecked flag determines if assertions are checked and validat...
Definition Assertions.h:48
#define RequireNotNull(p)
Definition Assertions.h:347
#define AssertNotReached()
Definition Assertions.h:355
Results< FLOAT_TYPE > Run(const TargetFunction< FLOAT_TYPE > &function2Minimize, const Sequence< FLOAT_TYPE > &initialValues, const Options< FLOAT_TYPE > &options=Options< FLOAT_TYPE >{})
Downhill Simplex Minimization, AKA Nelder-Mead algorithm, to compute minimization.
time_point< RealtimeClock, DurationSeconds > TimePointSeconds
TimePointSeconds is a simpler approach to chrono::time_point, which doesn't require using templates e...
Definition Realtime.h:82
chrono::duration< double > DurationSeconds
chrono::duration<double> - a time span (length of time) measured in seconds, but high precision.
Definition Realtime.h:57
#define DbgTrace
Definition Trace.h:309
#define Stroika_Foundation_Debug_OptionalizeTraceArgs(...)
Definition Trace.h:270
Similar to String, but intended to more efficiently construct a String. Mutable type (String is large...
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
A Collection<T> is a container to manage an un-ordered collection of items, without equality defined ...
Definition Collection.h:102
nonvirtual void Update(const Iterator< value_type > &i, ArgByValueType< value_type > newValue, Iterator< value_type > *nextI=nullptr)
nonvirtual void Remove(ArgByValueType< value_type > item, EQUALS_COMPARER &&equalsComparer={})
Remove () the argument value (which must exist)
nonvirtual void Add(ArgByValueType< value_type > item)
nonvirtual void RemoveAll()
RemoveAll removes all, or all matching (predicate, iterator range, equals comparer or whatever) items...
Thread::Ptr is a (unsynchronized) smart pointer referencing an internally synchronized std::thread ob...
Definition Thread.h:334
nonvirtual void AbortAndWaitForDone(Time::DurationSeconds timeout=Time::kInfinity) const
Abort () the thread, and then WaitForDone () - but if doesn't finish fast enough, send extra aborts (...
Definition Thread.inl:291
nonvirtual size_t GetPendingTasksCount() const
return GetPendingTasks ().size (), except probably much more efficient
nonvirtual bool IsRunning(const TaskType &task) const
nonvirtual Containers::Collection< TaskType > GetRunningTasks() const
nonvirtual void AbortTasks(Time::DurationSeconds timeout=Time::kInfinity)
nonvirtual Containers::Collection< TaskInfo > GetTasks() const
returns GetPendingTasks () + GetRunningTasks () - but also some extra information about each task
nonvirtual Statistics GetCurrentStatistics() const
nonvirtual void AbortTask(const TaskType &task, Time::DurationSeconds timeout=Time::kInfinity)
nonvirtual TaskType AddTask(const TaskType &task, const optional< Characters::String > &name=nullopt)
nonvirtual size_t GetTasksCount() const
return total number of tasks, either pending, or currently running.
nonvirtual Characters::String ToString() const
nonvirtual bool IsPresent(const TaskType &task) const
nonvirtual void WaitForTask(const TaskType &task, Time::DurationSeconds timeout=Time::kInfinity) const
nonvirtual void SetPoolSize(unsigned int poolSize)
nonvirtual void WaitForTasksDoneUntil(const Traversal::Iterable< TaskType > &tasks, Time::TimePointSeconds timeoutAt) const
nonvirtual Containers::Collection< TaskType > GetPendingTasks() const
nonvirtual unsigned int GetPoolSize() const
nonvirtual void WaitAndReset(Time::Duration timeout=Time::kInfinity)
Iterable<T> is a base class for containers which easily produce an Iterator<T> to traverse them.
Definition Iterable.h:237
nonvirtual void Apply(const function< void(ArgByValueType< T > item)> &doToElement, Execution::SequencePolicy seq=Execution::SequencePolicy::eDEFAULT) const
Run the argument function (or lambda) on each element of the container.
nonvirtual size_t size() const
Returns the number of items contained.
Definition Iterable.inl:300
nonvirtual Iterator< T > begin() const
Support for ranged for, and STL syntax in general.
nonvirtual bool empty() const
Returns true iff size() == 0.
Definition Iterable.inl:306
static constexpr default_sentinel_t end() noexcept
Support for ranged for, and STL syntax in general.
An Iterator<T> is a copyable object which allows traversing the contents of some container....
Definition Iterator.h:225
Ptr New(const function< void()> &fun2CallOnce, const optional< Characters::String > &name, const optional< Configuration > &configuration)
Definition Thread.cpp:955
void AbortAndWaitForDone(const Traversal::Iterable< Ptr > &threads, Time::DurationSeconds timeout=Time::kInfinity)
shorthand for AbortAndWaitForDoneUntil (Time::GetTickCount () + timeout)
Definition Thread.inl:343
void Sleep(Time::Duration seconds2Wait)
Definition Sleep.cpp:18
void ThrowTimeoutExceptionAfter(Time::TimePointSeconds afterTickCount, EXCEPTION &&exception2Throw)
Throw TimeOutException if the @Time::GetTickCount () is >= the given value.
void Throw(T &&e2Throw)
identical to builtin C++ 'throw' except that it does helpful, type dependent DbgTrace() messages firs...
Definition Throw.inl:43
auto Finally(FUNCTION &&f) -> Private_::FinallySentry< FUNCTION >
Definition Finally.inl:31
nonvirtual Characters::String ToString() const