1 /* 2 Copyright (c) 2005-2022 Intel Corporation 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 #ifndef __TBB_task_group_H 18 #define __TBB_task_group_H 19 20 #include "detail/_config.h" 21 #include "detail/_namespace_injection.h" 22 #include "detail/_assert.h" 23 #include "detail/_utils.h" 24 #include "detail/_template_helpers.h" 25 #include "detail/_exception.h" 26 #include "detail/_task.h" 27 #include "detail/_small_object_pool.h" 28 #include "detail/_intrusive_list_node.h" 29 #include "detail/_task_handle.h" 30 31 #include "profiling.h" 32 33 #include <type_traits> 34 35 #if _MSC_VER && !defined(__INTEL_COMPILER) 36 // Suppress warning: structure was padded due to alignment specifier 37 #pragma warning(push) 38 #pragma warning(disable:4324) 39 #endif 40 41 namespace tbb { 42 namespace detail { 43 44 namespace d1 { 45 class delegate_base; 46 class task_arena_base; 47 class task_group_context; 48 class task_group_base; 49 } 50 51 namespace r1 { 52 // Forward declarations 53 class tbb_exception_ptr; 54 class market; 55 class thread_data; 56 class task_dispatcher; 57 template <bool> 58 class context_guard_helper; 59 struct task_arena_impl; 60 class context_list; 61 62 TBB_EXPORT void __TBB_EXPORTED_FUNC execute(d1::task_arena_base&, d1::delegate_base&); 63 TBB_EXPORT void __TBB_EXPORTED_FUNC isolate_within_arena(d1::delegate_base&, std::intptr_t); 64 65 TBB_EXPORT void __TBB_EXPORTED_FUNC initialize(d1::task_group_context&); 66 TBB_EXPORT void __TBB_EXPORTED_FUNC destroy(d1::task_group_context&); 67 TBB_EXPORT void __TBB_EXPORTED_FUNC reset(d1::task_group_context&); 68 TBB_EXPORT bool __TBB_EXPORTED_FUNC cancel_group_execution(d1::task_group_context&); 69 TBB_EXPORT bool __TBB_EXPORTED_FUNC is_group_execution_cancelled(d1::task_group_context&); 70 TBB_EXPORT void __TBB_EXPORTED_FUNC capture_fp_settings(d1::task_group_context&); 71 72 struct task_group_context_impl; 73 } 74 75 namespace d2 { 76 77 namespace { 78 template<typename F> 79 d1::task* task_ptr_or_nullptr(F&& f); 80 } 81 82 template<typename F> 83 class function_task : public task_handle_task { 84 //TODO: apply empty base optimization here 85 const F m_func; 86 87 private: 88 d1::task* execute(d1::execution_data& ed) override { 89 __TBB_ASSERT(ed.context == &this->ctx(), "The task group context should be used for all tasks"); 90 task* res = task_ptr_or_nullptr(m_func); 91 finalize(&ed); 92 return res; 93 } 94 d1::task* cancel(d1::execution_data& ed) override { 95 finalize(&ed); 96 return nullptr; 97 } 98 public: 99 template<typename FF> 100 function_task(FF&& f, d1::wait_context& wo, d1::task_group_context& ctx, d1::small_object_allocator& alloc) 101 : task_handle_task{wo, ctx, alloc}, 102 m_func(std::forward<FF>(f)) {} 103 }; 104 105 #if __TBB_PREVIEW_TASK_GROUP_EXTENSIONS 106 namespace { 107 template<typename F> 108 d1::task* task_ptr_or_nullptr_impl(std::false_type, F&& f){ 109 task_handle th = std::forward<F>(f)(); 110 return task_handle_accessor::release(th); 111 } 112 113 template<typename F> 114 d1::task* task_ptr_or_nullptr_impl(std::true_type, F&& f){ 115 std::forward<F>(f)(); 116 return nullptr; 117 } 118 119 template<typename F> 120 d1::task* task_ptr_or_nullptr(F&& f){ 121 using is_void_t = std::is_void< 122 decltype(std::forward<F>(f)()) 123 >; 124 125 return task_ptr_or_nullptr_impl(is_void_t{}, std::forward<F>(f)); 126 } 127 } 128 #else 129 namespace { 130 template<typename F> 131 d1::task* task_ptr_or_nullptr(F&& f){ 132 std::forward<F>(f)(); 133 return nullptr; 134 } 135 } // namespace 136 #endif // __TBB_PREVIEW_TASK_GROUP_EXTENSIONS 137 } // namespace d2 138 139 namespace d1 { 140 141 // This structure is left here for backward compatibility check 142 struct context_list_node { 143 std::atomic<context_list_node*> prev{}; 144 std::atomic<context_list_node*> next{}; 145 }; 146 147 //! Used to form groups of tasks 148 /** @ingroup task_scheduling 149 The context services explicit cancellation requests from user code, and unhandled 150 exceptions intercepted during tasks execution. Intercepting an exception results 151 in generating internal cancellation requests (which is processed in exactly the 152 same way as external ones). 153 154 The context is associated with one or more root tasks and defines the cancellation 155 group that includes all the descendants of the corresponding root task(s). Association 156 is established when a context object is passed as an argument to the task::allocate_root() 157 method. See task_group_context::task_group_context for more details. 158 159 The context can be bound to another one, and other contexts can be bound to it, 160 forming a tree-like structure: parent -> this -> children. Arrows here designate 161 cancellation propagation direction. If a task in a cancellation group is cancelled 162 all the other tasks in this group and groups bound to it (as children) get cancelled too. 163 **/ 164 class task_group_context : no_copy { 165 public: 166 enum traits_type { 167 fp_settings = 1 << 1, 168 concurrent_wait = 1 << 2, 169 default_traits = 0 170 }; 171 enum kind_type { 172 isolated, 173 bound 174 }; 175 private: 176 //! Space for platform-specific FPU settings. 177 /** Must only be accessed inside TBB binaries, and never directly in user 178 code or inline methods. */ 179 std::uint64_t my_cpu_ctl_env; 180 181 //! Specifies whether cancellation was requested for this task group. 182 std::atomic<std::uint32_t> my_cancellation_requested; 183 184 //! Versioning for run-time checks and behavioral traits of the context. 185 enum class task_group_context_version : std::uint8_t { 186 unused = 1 // ensure that new versions, if any, will not clash with previously used ones 187 }; 188 task_group_context_version my_version; 189 190 //! The context traits. 191 struct context_traits { 192 bool fp_settings : 1; 193 bool concurrent_wait : 1; 194 bool bound : 1; 195 bool reserved1 : 1; 196 bool reserved2 : 1; 197 bool reserved3 : 1; 198 bool reserved4 : 1; 199 bool reserved5 : 1; 200 } my_traits; 201 202 static_assert(sizeof(context_traits) == 1, "Traits shall fit into one byte."); 203 204 static constexpr std::uint8_t may_have_children = 1; 205 //! The context internal state (currently only may_have_children). 206 std::atomic<std::uint8_t> my_may_have_children; 207 208 enum class state : std::uint8_t { 209 created, 210 locked, 211 isolated, 212 bound, 213 dead, 214 proxy = std::uint8_t(-1) //the context is not the real one, but proxy to other one 215 }; 216 217 //! The synchronization machine state to manage lifetime. 218 std::atomic<state> my_state; 219 220 union { 221 //! Pointer to the context of the parent cancellation group. nullptr for isolated contexts. 222 task_group_context* my_parent; 223 224 //! Pointer to the actual context 'this' context represents a proxy of. 225 task_group_context* my_actual_context; 226 }; 227 228 //! Thread data instance that registered this context in its list. 229 r1::context_list* my_context_list; 230 static_assert(sizeof(std::atomic<r1::thread_data*>) == sizeof(r1::context_list*), "To preserve backward compatibility these types should have the same size"); 231 232 //! Used to form the thread specific list of contexts without additional memory allocation. 233 /** A context is included into the list of the current thread when its binding to 234 its parent happens. Any context can be present in the list of one thread only. **/ 235 intrusive_list_node my_node; 236 static_assert(sizeof(intrusive_list_node) == sizeof(context_list_node), "To preserve backward compatibility these types should have the same size"); 237 238 //! Pointer to the container storing exception being propagated across this task group. 239 std::atomic<r1::tbb_exception_ptr*> my_exception; 240 static_assert(sizeof(std::atomic<r1::tbb_exception_ptr*>) == sizeof(r1::tbb_exception_ptr*), 241 "backward compatibility check"); 242 243 //! Used to set and maintain stack stitching point for Intel Performance Tools. 244 void* my_itt_caller; 245 246 //! Description of algorithm for scheduler based instrumentation. 247 string_resource_index my_name; 248 249 char padding[max_nfs_size 250 - sizeof(std::uint64_t) // my_cpu_ctl_env 251 - sizeof(std::atomic<std::uint32_t>) // my_cancellation_requested 252 - sizeof(std::uint8_t) // my_version 253 - sizeof(context_traits) // my_traits 254 - sizeof(std::atomic<std::uint8_t>) // my_state 255 - sizeof(std::atomic<state>) // my_state 256 - sizeof(task_group_context*) // my_parent 257 - sizeof(r1::context_list*) // my_context_list 258 - sizeof(intrusive_list_node) // my_node 259 - sizeof(std::atomic<r1::tbb_exception_ptr*>) // my_exception 260 - sizeof(void*) // my_itt_caller 261 - sizeof(string_resource_index) // my_name 262 ]; 263 264 task_group_context(context_traits t, string_resource_index name) 265 : my_version{task_group_context_version::unused}, my_name{name} 266 { 267 my_traits = t; // GCC4.8 issues warning list initialization for bitset (missing-field-initializers) 268 r1::initialize(*this); 269 } 270 271 task_group_context(task_group_context* actual_context) 272 : my_version{task_group_context_version::unused} 273 , my_state{state::proxy} 274 , my_actual_context{actual_context} 275 { 276 __TBB_ASSERT(my_actual_context, "Passed pointer value points to nothing."); 277 my_name = actual_context->my_name; 278 279 // no need to initialize 'this' context as it acts as a proxy for my_actual_context, which 280 // initialization is a user-side responsibility. 281 } 282 283 static context_traits make_traits(kind_type relation_with_parent, std::uintptr_t user_traits) { 284 context_traits ct; 285 ct.fp_settings = (user_traits & fp_settings) == fp_settings; 286 ct.concurrent_wait = (user_traits & concurrent_wait) == concurrent_wait; 287 ct.bound = relation_with_parent == bound; 288 ct.reserved1 = ct.reserved2 = ct.reserved3 = ct.reserved4 = ct.reserved5 = false; 289 return ct; 290 } 291 292 bool is_proxy() const { 293 return my_state.load(std::memory_order_relaxed) == state::proxy; 294 } 295 296 task_group_context& actual_context() noexcept { 297 if (is_proxy()) { 298 __TBB_ASSERT(my_actual_context, "Actual task_group_context is not set."); 299 return *my_actual_context; 300 } 301 return *this; 302 } 303 304 const task_group_context& actual_context() const noexcept { 305 if (is_proxy()) { 306 __TBB_ASSERT(my_actual_context, "Actual task_group_context is not set."); 307 return *my_actual_context; 308 } 309 return *this; 310 } 311 312 public: 313 //! Default & binding constructor. 314 /** By default a bound context is created. That is this context will be bound 315 (as child) to the context of the currently executing task . Cancellation 316 requests passed to the parent context are propagated to all the contexts 317 bound to it. Similarly priority change is propagated from the parent context 318 to its children. 319 320 If task_group_context::isolated is used as the argument, then the tasks associated 321 with this context will never be affected by events in any other context. 322 323 Creating isolated contexts involve much less overhead, but they have limited 324 utility. Normally when an exception occurs in an algorithm that has nested 325 ones running, it is desirably to have all the nested algorithms cancelled 326 as well. Such a behavior requires nested algorithms to use bound contexts. 327 328 There is one good place where using isolated algorithms is beneficial. It is 329 an external thread. That is if a particular algorithm is invoked directly from 330 the external thread (not from a TBB task), supplying it with explicitly 331 created isolated context will result in a faster algorithm startup. 332 333 VERSIONING NOTE: 334 Implementation(s) of task_group_context constructor(s) cannot be made 335 entirely out-of-line because the run-time version must be set by the user 336 code. This will become critically important for binary compatibility, if 337 we ever have to change the size of the context object. **/ 338 339 task_group_context(kind_type relation_with_parent = bound, 340 std::uintptr_t t = default_traits) 341 : task_group_context(make_traits(relation_with_parent, t), CUSTOM_CTX) {} 342 343 // Custom constructor for instrumentation of oneTBB algorithm 344 task_group_context(string_resource_index name ) 345 : task_group_context(make_traits(bound, default_traits), name) {} 346 347 // Do not introduce any logic on user side since it might break state propagation assumptions 348 ~task_group_context() { 349 // When 'this' serves as a proxy, the initialization does not happen - nor should the 350 // destruction. 351 if (!is_proxy()) 352 { 353 r1::destroy(*this); 354 } 355 } 356 357 //! Forcefully reinitializes the context after the task tree it was associated with is completed. 358 /** Because the method assumes that all the tasks that used to be associated with 359 this context have already finished, calling it while the context is still 360 in use somewhere in the task hierarchy leads to undefined behavior. 361 362 IMPORTANT: This method is not thread safe! 363 364 The method does not change the context's parent if it is set. **/ 365 void reset() { 366 r1::reset(actual_context()); 367 } 368 369 //! Initiates cancellation of all tasks in this cancellation group and its subordinate groups. 370 /** \return false if cancellation has already been requested, true otherwise. 371 372 Note that canceling never fails. When false is returned, it just means that 373 another thread (or this one) has already sent cancellation request to this 374 context or to one of its ancestors (if this context is bound). It is guaranteed 375 that when this method is concurrently called on the same not yet cancelled 376 context, true will be returned by one and only one invocation. **/ 377 bool cancel_group_execution() { 378 return r1::cancel_group_execution(actual_context()); 379 } 380 381 //! Returns true if the context received cancellation request. 382 bool is_group_execution_cancelled() { 383 return r1::is_group_execution_cancelled(actual_context()); 384 } 385 386 #if __TBB_FP_CONTEXT 387 //! Captures the current FPU control settings to the context. 388 /** Because the method assumes that all the tasks that used to be associated with 389 this context have already finished, calling it while the context is still 390 in use somewhere in the task hierarchy leads to undefined behavior. 391 392 IMPORTANT: This method is not thread safe! 393 394 The method does not change the FPU control settings of the context's parent. **/ 395 void capture_fp_settings() { 396 r1::capture_fp_settings(actual_context()); 397 } 398 #endif 399 400 //! Returns the user visible context trait 401 std::uintptr_t traits() const { 402 std::uintptr_t t{}; 403 const task_group_context& ctx = actual_context(); 404 t |= ctx.my_traits.fp_settings ? fp_settings : 0; 405 t |= ctx.my_traits.concurrent_wait ? concurrent_wait : 0; 406 return t; 407 } 408 private: 409 //// TODO: cleanup friends 410 friend class r1::market; 411 friend class r1::thread_data; 412 friend class r1::task_dispatcher; 413 template <bool> 414 friend class r1::context_guard_helper; 415 friend struct r1::task_arena_impl; 416 friend struct r1::task_group_context_impl; 417 friend class task_group_base; 418 }; // class task_group_context 419 420 static_assert(sizeof(task_group_context) == 128, "Wrong size of task_group_context"); 421 422 enum task_group_status { 423 not_complete, 424 complete, 425 canceled 426 }; 427 428 class task_group; 429 class structured_task_group; 430 #if TBB_PREVIEW_ISOLATED_TASK_GROUP 431 class isolated_task_group; 432 #endif 433 434 template<typename F> 435 class function_task : public task { 436 const F m_func; 437 wait_context& m_wait_ctx; 438 small_object_allocator m_allocator; 439 440 void finalize(const execution_data& ed) { 441 // Make a local reference not to access this after destruction. 442 wait_context& wo = m_wait_ctx; 443 // Copy allocator to the stack 444 auto allocator = m_allocator; 445 // Destroy user functor before release wait. 446 this->~function_task(); 447 wo.release(); 448 449 allocator.deallocate(this, ed); 450 } 451 task* execute(execution_data& ed) override { 452 task* res = d2::task_ptr_or_nullptr(m_func); 453 finalize(ed); 454 return res; 455 } 456 task* cancel(execution_data& ed) override { 457 finalize(ed); 458 return nullptr; 459 } 460 public: 461 function_task(const F& f, wait_context& wo, small_object_allocator& alloc) 462 : m_func(f) 463 , m_wait_ctx(wo) 464 , m_allocator(alloc) {} 465 466 function_task(F&& f, wait_context& wo, small_object_allocator& alloc) 467 : m_func(std::move(f)) 468 , m_wait_ctx(wo) 469 , m_allocator(alloc) {} 470 }; 471 472 template <typename F> 473 class function_stack_task : public task { 474 const F& m_func; 475 wait_context& m_wait_ctx; 476 477 void finalize() { 478 m_wait_ctx.release(); 479 } 480 task* execute(execution_data&) override { 481 task* res = d2::task_ptr_or_nullptr(m_func); 482 finalize(); 483 return res; 484 } 485 task* cancel(execution_data&) override { 486 finalize(); 487 return nullptr; 488 } 489 public: 490 function_stack_task(const F& f, wait_context& wo) : m_func(f), m_wait_ctx(wo) {} 491 }; 492 493 class task_group_base : no_copy { 494 protected: 495 wait_context m_wait_ctx; 496 task_group_context m_context; 497 498 template<typename F> 499 task_group_status internal_run_and_wait(const F& f) { 500 function_stack_task<F> t{ f, m_wait_ctx }; 501 m_wait_ctx.reserve(); 502 bool cancellation_status = false; 503 try_call([&] { 504 execute_and_wait(t, context(), m_wait_ctx, context()); 505 }).on_completion([&] { 506 // TODO: the reset method is not thread-safe. Ensure the correct behavior. 507 cancellation_status = context().is_group_execution_cancelled(); 508 context().reset(); 509 }); 510 return cancellation_status ? canceled : complete; 511 } 512 513 task_group_status internal_run_and_wait(d2::task_handle&& h) { 514 __TBB_ASSERT(h != nullptr, "Attempt to schedule empty task_handle"); 515 516 using acs = d2::task_handle_accessor; 517 __TBB_ASSERT(&acs::ctx_of(h) == &context(), "Attempt to schedule task_handle into different task_group"); 518 519 bool cancellation_status = false; 520 try_call([&] { 521 execute_and_wait(*acs::release(h), context(), m_wait_ctx, context()); 522 }).on_completion([&] { 523 // TODO: the reset method is not thread-safe. Ensure the correct behavior. 524 cancellation_status = context().is_group_execution_cancelled(); 525 context().reset(); 526 }); 527 return cancellation_status ? canceled : complete; 528 } 529 530 template<typename F> 531 task* prepare_task(F&& f) { 532 m_wait_ctx.reserve(); 533 small_object_allocator alloc{}; 534 return alloc.new_object<function_task<typename std::decay<F>::type>>(std::forward<F>(f), m_wait_ctx, alloc); 535 } 536 537 task_group_context& context() noexcept { 538 return m_context.actual_context(); 539 } 540 541 template<typename F> 542 d2::task_handle prepare_task_handle(F&& f) { 543 m_wait_ctx.reserve(); 544 small_object_allocator alloc{}; 545 using function_task_t = d2::function_task<typename std::decay<F>::type>; 546 d2::task_handle_task* function_task_p = alloc.new_object<function_task_t>(std::forward<F>(f), m_wait_ctx, context(), alloc); 547 548 return d2::task_handle_accessor::construct(function_task_p); 549 } 550 551 public: 552 task_group_base(uintptr_t traits = 0) 553 : m_wait_ctx(0) 554 , m_context(task_group_context::bound, task_group_context::default_traits | traits) 555 {} 556 557 task_group_base(task_group_context& ctx) 558 : m_wait_ctx(0) 559 , m_context(&ctx) 560 {} 561 562 ~task_group_base() noexcept(false) { 563 if (m_wait_ctx.continue_execution()) { 564 #if __TBB_CPP17_UNCAUGHT_EXCEPTIONS_PRESENT 565 bool stack_unwinding_in_progress = std::uncaught_exceptions() > 0; 566 #else 567 bool stack_unwinding_in_progress = std::uncaught_exception(); 568 #endif 569 // Always attempt to do proper cleanup to avoid inevitable memory corruption 570 // in case of missing wait (for the sake of better testability & debuggability) 571 if (!context().is_group_execution_cancelled()) 572 cancel(); 573 d1::wait(m_wait_ctx, context()); 574 if (!stack_unwinding_in_progress) 575 throw_exception(exception_id::missing_wait); 576 } 577 } 578 579 task_group_status wait() { 580 bool cancellation_status = false; 581 try_call([&] { 582 d1::wait(m_wait_ctx, context()); 583 }).on_completion([&] { 584 // TODO: the reset method is not thread-safe. Ensure the correct behavior. 585 cancellation_status = m_context.is_group_execution_cancelled(); 586 context().reset(); 587 }); 588 return cancellation_status ? canceled : complete; 589 } 590 591 void cancel() { 592 context().cancel_group_execution(); 593 } 594 }; // class task_group_base 595 596 class task_group : public task_group_base { 597 public: 598 task_group() : task_group_base(task_group_context::concurrent_wait) {} 599 task_group(task_group_context& ctx) : task_group_base(ctx) {} 600 601 template<typename F> 602 void run(F&& f) { 603 spawn(*prepare_task(std::forward<F>(f)), context()); 604 } 605 606 void run(d2::task_handle&& h) { 607 __TBB_ASSERT(h != nullptr, "Attempt to schedule empty task_handle"); 608 609 using acs = d2::task_handle_accessor; 610 __TBB_ASSERT(&acs::ctx_of(h) == &context(), "Attempt to schedule task_handle into different task_group"); 611 612 spawn(*acs::release(h), context()); 613 } 614 615 template<typename F> 616 d2::task_handle defer(F&& f) { 617 return prepare_task_handle(std::forward<F>(f)); 618 619 } 620 621 template<typename F> 622 task_group_status run_and_wait(const F& f) { 623 return internal_run_and_wait(f); 624 } 625 626 task_group_status run_and_wait(d2::task_handle&& h) { 627 return internal_run_and_wait(std::move(h)); 628 } 629 }; // class task_group 630 631 #if TBB_PREVIEW_ISOLATED_TASK_GROUP 632 class spawn_delegate : public delegate_base { 633 task* task_to_spawn; 634 task_group_context& context; 635 bool operator()() const override { 636 spawn(*task_to_spawn, context); 637 return true; 638 } 639 public: 640 spawn_delegate(task* a_task, task_group_context& ctx) 641 : task_to_spawn(a_task), context(ctx) 642 {} 643 }; 644 645 class wait_delegate : public delegate_base { 646 bool operator()() const override { 647 status = tg.wait(); 648 return true; 649 } 650 protected: 651 task_group& tg; 652 task_group_status& status; 653 public: 654 wait_delegate(task_group& a_group, task_group_status& tgs) 655 : tg(a_group), status(tgs) {} 656 }; 657 658 template<typename F> 659 class run_wait_delegate : public wait_delegate { 660 F& func; 661 bool operator()() const override { 662 status = tg.run_and_wait(func); 663 return true; 664 } 665 public: 666 run_wait_delegate(task_group& a_group, F& a_func, task_group_status& tgs) 667 : wait_delegate(a_group, tgs), func(a_func) {} 668 }; 669 670 class isolated_task_group : public task_group { 671 intptr_t this_isolation() { 672 return reinterpret_cast<intptr_t>(this); 673 } 674 public: 675 isolated_task_group() : task_group() {} 676 677 isolated_task_group(task_group_context& ctx) : task_group(ctx) {} 678 679 template<typename F> 680 void run(F&& f) { 681 spawn_delegate sd(prepare_task(std::forward<F>(f)), context()); 682 r1::isolate_within_arena(sd, this_isolation()); 683 } 684 685 void run(d2::task_handle&& h) { 686 __TBB_ASSERT(h != nullptr, "Attempt to schedule empty task_handle"); 687 688 using acs = d2::task_handle_accessor; 689 __TBB_ASSERT(&acs::ctx_of(h) == &context(), "Attempt to schedule task_handle into different task_group"); 690 691 spawn_delegate sd(acs::release(h), context()); 692 r1::isolate_within_arena(sd, this_isolation()); 693 } 694 695 template<typename F> 696 task_group_status run_and_wait( const F& f ) { 697 task_group_status result = not_complete; 698 run_wait_delegate<const F> rwd(*this, f, result); 699 r1::isolate_within_arena(rwd, this_isolation()); 700 __TBB_ASSERT(result != not_complete, "premature exit from wait?"); 701 return result; 702 } 703 704 task_group_status wait() { 705 task_group_status result = not_complete; 706 wait_delegate wd(*this, result); 707 r1::isolate_within_arena(wd, this_isolation()); 708 __TBB_ASSERT(result != not_complete, "premature exit from wait?"); 709 return result; 710 } 711 }; // class isolated_task_group 712 #endif // TBB_PREVIEW_ISOLATED_TASK_GROUP 713 714 inline bool is_current_task_group_canceling() { 715 task_group_context* ctx = current_context(); 716 return ctx ? ctx->is_group_execution_cancelled() : false; 717 } 718 719 } // namespace d1 720 } // namespace detail 721 722 inline namespace v1 { 723 using detail::d1::task_group_context; 724 using detail::d1::task_group; 725 #if TBB_PREVIEW_ISOLATED_TASK_GROUP 726 using detail::d1::isolated_task_group; 727 #endif 728 729 using detail::d1::task_group_status; 730 using detail::d1::not_complete; 731 using detail::d1::complete; 732 using detail::d1::canceled; 733 734 using detail::d1::is_current_task_group_canceling; 735 using detail::r1::missing_wait; 736 737 using detail::d2::task_handle; 738 } 739 740 } // namespace tbb 741 742 #if _MSC_VER && !defined(__INTEL_COMPILER) 743 #pragma warning(pop) // 4324 warning 744 #endif 745 746 #endif // __TBB_task_group_H 747