ห้องสมุด 'CPPCORO' ให้ชุดดั้งเดิมขนาดใหญ่ที่มีวัตถุประสงค์ทั่วไปสำหรับการใช้ข้อเสนอ coroutines TS ที่อธิบายไว้ใน N4680
เหล่านี้รวมถึง:
task<T>shared_task<T>generator<T>recursive_generator<T>async_generator<T>single_consumer_eventsingle_consumer_async_auto_reset_eventasync_mutexasync_manual_reset_eventasync_auto_reset_eventasync_latchsequence_barriermulti_producer_sequencersingle_producer_sequencersync_wait()when_all()when_all_ready()fmap()schedule_on()resume_on()cancellation_tokencancellation_sourcecancellation_registrationstatic_thread_poolio_service และ io_work_scopefile , readable_file , writable_fileread_only_file , write_only_file , read_write_filesocketip_address , ipv4_address , ipv6_addressip_endpoint , ipv4_endpoint , ipv6_endpointis_awaitable<T>awaitable_traits<T>Awaitable<T>Awaiter<T>SchedulerDelayedSchedulerห้องสมุดนี้เป็นห้องสมุดทดลองที่สำรวจพื้นที่ของการเขียนโปรแกรมแบบอะซิงโครนัสที่ปรับขนาดได้สูง
มันได้รับการเปิดกว้างด้วยความหวังว่าคนอื่นจะพบว่ามีประโยชน์และชุมชน C ++ สามารถให้ข้อเสนอแนะเกี่ยวกับมันและวิธีการปรับปรุง
ต้องใช้คอมไพเลอร์ที่รองรับ coroutines TS:
เวอร์ชัน Linux นั้นใช้งานได้ยกเว้นคลาส io_context และไฟล์ I/O ที่เกี่ยวข้องซึ่งยังไม่ได้ใช้งานสำหรับ Linux (ดูปัญหา #15 สำหรับข้อมูลเพิ่มเติม)
task<T>งานแสดงถึงการคำนวณแบบอะซิงโครนัสที่ดำเนินการอย่างเกียจคร้านในการดำเนินการของ coroutine ไม่เริ่มจนกว่างานจะรอ
ตัวอย่าง:
# include < cppcoro/read_only_file.hpp >
# include < cppcoro/task.hpp >
cppcoro::task< int > count_lines (std::string path)
{
auto file = co_await cppcoro::read_only_file::open (path);
int lineCount = 0 ;
char buffer[ 1024 ];
size_t bytesRead;
std:: uint64_t offset = 0 ;
do
{
bytesRead = co_await file. read (offset, buffer, sizeof (buffer));
lineCount += std::count (buffer, buffer + bytesRead, ' n ' );
offset += bytesRead;
} while (bytesRead > 0 );
co_return lineCount;
}
cppcoro::task<> usage_example ()
{
// Calling function creates a new task but doesn't start
// executing the coroutine yet.
cppcoro::task< int > countTask = count_lines ( " foo.txt " );
// ...
// Coroutine is only started when we later co_await the task.
int lineCount = co_await countTask;
std::cout << " line count = " << lineCount << std::endl;
}ภาพรวม API:
// <cppcoro/task.hpp>
namespace cppcoro
{
template < typename T>
class task
{
public:
using promise_type = <unspecified>;
using value_type = T;
task () noexcept ;
task (task&& other) noexcept ;
task& operator =(task&& other);
// task is a move-only type.
task ( const task& other) = delete ;
task& operator =( const task& other) = delete ;
// Query if the task result is ready.
bool is_ready () const noexcept ;
// Wait for the task to complete and return the result or rethrow the
// exception if the operation completed with an unhandled exception.
//
// If the task is not yet ready then the awaiting coroutine will be
// suspended until the task completes. If the the task is_ready() then
// this operation will return the result synchronously without suspending.
Awaiter<T&> operator co_await () const & noexcept ;
Awaiter<T&&> operator co_await () const && noexcept ;
// Returns an awaitable that can be co_await'ed to suspend the current
// coroutine until the task completes.
//
// The 'co_await t.when_ready()' expression differs from 'co_await t' in
// that when_ready() only performs synchronization, it does not return
// the result or rethrow the exception.
//
// This can be useful if you want to synchronize with the task without
// the possibility of it throwing an exception.
Awaitable< void > when_ready () const noexcept ;
};
template < typename T>
void swap (task<T>& a, task<T>& b);
// Creates a task that yields the result of co_await'ing the specified awaitable.
//
// This can be used as a form of type-erasure of the concrete awaitable, allowing
// different awaitables that return the same await-result type to be stored in
// the same task<RESULT> type.
template <
typename AWAITABLE,
typename RESULT = typename awaitable_traits<AWAITABLE>:: await_result_t >
task<RESULT> make_task (AWAITABLE awaitable);
} คุณสามารถสร้างวัตถุ task<T> โดยเรียกฟังก์ชั่น coroutine ที่ส่งคืน task<T>
coroutine จะต้องมีการใช้งานของ co_await หรือ co_return โปรดทราบว่า task<T> coroutine อาจไม่ใช้คำหลัก co_yield
เมื่อมีการเรียก coroutine ที่ส่งคืน task<T> เฟรม coroutine จะถูกจัดสรรหากจำเป็นและพารามิเตอร์จะถูกจับในกรอบ coroutine coroutine ถูกระงับเมื่อเริ่มต้นของร่างกาย coroutine และการดำเนินการจะถูกส่งกลับไปยังผู้โทรและค่า task<T> ที่แสดงถึงการคำนวณแบบอะซิงโครนัสจะถูกส่งคืนจากการเรียกใช้ฟังก์ชัน
Coroutine Body จะเริ่มดำเนินการเมื่อ task<T> ค่าเป็น co_await ed สิ่งนี้จะระงับการรอ coroutine และเริ่มดำเนินการของ coroutine ที่เกี่ยวข้องกับค่าที่รอคอย task<T>
coroutine ที่รอคอยจะกลับมาทำงานต่อในเธรดที่เสร็จสิ้นการดำเนินการของ task<T> ของ Coroutine เช่น. เธรดที่ดำเนินการ co_return หรือที่โยนข้อยกเว้นที่ไม่มีการจัดการซึ่งยุติการดำเนินการของ coroutine
หากงานได้ดำเนินไปจนเสร็จสิ้นการรอคอยอีกครั้งจะได้รับผลลัพธ์ที่คำนวณแล้วโดยไม่ระงับการรอ coroutine
หากวัตถุ task ถูกทำลายก่อนที่จะรอคอย coroutine จะไม่ถูกเรียกใช้งานและ destructor จะทำลายพารามิเตอร์ที่จับได้และปลดปล่อยหน่วยความจำใด ๆ ที่ใช้โดยกรอบ coroutine
shared_task<T> คลาส shared_task<T> เป็นประเภท coroutine ที่ให้ค่าเดียวแบบอะซิงโครนัส
มันเป็น 'ขี้เกียจ' ในการดำเนินงานนั้นไม่ได้เริ่มต้นจนกว่าจะได้รับการรอคอยโดย coroutine
มันคือ 'แบ่งปัน' ซึ่งสามารถคัดลอกค่างานได้ซึ่งช่วยให้การอ้างอิงหลายครั้งกับผลลัพธ์ของงานที่จะสร้าง นอกจากนี้ยังช่วยให้ coroutines หลายตัวรอผลพร้อมกัน
งานจะเริ่มดำเนินการบนเธรดที่ co_await เป็นงานแรก การรอคอยที่ตามมาจะถูกระงับและถูกคิวเพื่อเริ่มต้นใหม่เมื่องานเสร็จสมบูรณ์หรือจะดำเนินการต่อไปหากงานได้ดำเนินการเสร็จแล้ว
หากผู้รอคอยถูกระงับในขณะที่รอให้งานเสร็จสมบูรณ์มันจะกลับมาทำงานต่อในเธรดที่เสร็จสิ้นการดำเนินการของงาน เช่น. เธรดที่ดำเนินการ co_return หรือที่โยนข้อยกเว้นที่ไม่มีการจัดการซึ่งยุติการดำเนินการของ coroutine
สรุป API
namespace cppcoro
{
template < typename T = void >
class shared_task
{
public:
using promise_type = <unspecified>;
using value_type = T;
shared_task () noexcept ;
shared_task ( const shared_task& other) noexcept ;
shared_task (shared_task&& other) noexcept ;
shared_task& operator =( const shared_task& other) noexcept ;
shared_task& operator =(shared_task&& other) noexcept ;
void swap (shared_task& other) noexcept ;
// Query if the task has completed and the result is ready.
bool is_ready () const noexcept ;
// Returns an operation that when awaited will suspend the
// current coroutine until the task completes and the result
// is available.
//
// The type of the result of the 'co_await someTask' expression
// is an l-value reference to the task's result value (unless T
// is void in which case the expression has type 'void').
// If the task completed with an unhandled exception then the
// exception will be rethrown by the co_await expression.
Awaiter<T&> operator co_await () const noexcept ;
// Returns an operation that when awaited will suspend the
// calling coroutine until the task completes and the result
// is available.
//
// The result is not returned from the co_await expression.
// This can be used to synchronize with the task without the
// possibility of the co_await expression throwing an exception.
Awaiter< void > when_ready () const noexcept ;
};
template < typename T>
bool operator ==( const shared_task<T>& a, const shared_task<T>& b) noexcept ;
template < typename T>
bool operator !=( const shared_task<T>& a, const shared_task<T>& b) noexcept ;
template < typename T>
void swap (shared_task<T>& a, shared_task<T>& b) noexcept ;
// Wrap an awaitable value in a shared_task to allow multiple coroutines
// to concurrently await the result.
template <
typename AWAITABLE,
typename RESULT = typename awaitable_traits<AWAITABLE>:: await_result_t >
shared_task<RESULT> make_shared_task (AWAITABLE awaitable);
} วิธีการทั้งหมดของ const บน shared_task<T> มีความปลอดภัยในการโทรพร้อมกับวิธีการอื่น ๆ ในอินสแตนซ์เดียวกันจากหลายเธรด มันไม่ปลอดภัยที่จะเรียกวิธีการที่ไม่ใช่ const ของ shared_task<T> พร้อมกันกับวิธีอื่นใดในอินสแตนซ์เดียวกันของ shared_task<T>
task<T> คลาส shared_task<T> นั้นคล้ายกับ task<T> ซึ่งงานจะไม่เริ่มดำเนินการทันทีเมื่อมีการเรียกฟังก์ชัน coroutine งานจะเริ่มดำเนินการเมื่อรอคอยเป็นครั้งแรก
มันแตกต่างจาก task<T> ในการที่วัตถุงานที่ได้สามารถคัดลอกได้ทำให้วัตถุงานหลายชิ้นอ้างอิงผลลัพธ์แบบอะซิงโครนัสเดียวกัน นอกจากนี้ยังรองรับ coroutines หลายตัวพร้อมกันรอผลของงาน
การแลกเปลี่ยนคือผลที่ได้คือการอ้างอิง L-value กับผลลัพธ์เสมออย่าอ้างอิงค่า R (เนื่องจากอาจมีการแบ่งปันผลลัพธ์) ซึ่งอาจจำกัดความสามารถในการย้ายผลลัพธ์ในตัวแปรท้องถิ่น นอกจากนี้ยังมีค่าใช้จ่ายในการทำงานที่สูงขึ้นเล็กน้อยเนื่องจากจำเป็นต้องรักษาจำนวนการอ้างอิงและสนับสนุนการรอคอยหลายครั้ง
generator<T> generator แสดงถึงประเภท coroutine ที่สร้างลำดับของค่าของประเภท, T โดยที่ค่าจะผลิตอย่างเกียจคร้านและซิงโครนัส
ร่างกาย Coroutine สามารถให้ค่าประเภท T โดยใช้คำหลัก co_yield อย่างไรก็ตามโปรดทราบว่าร่างกาย coroutine ไม่สามารถใช้คำหลัก co_await ; ค่าจะต้องผลิตแบบซิงโครนัส
ตัวอย่างเช่น:
cppcoro::generator< const std:: uint64_t > fibonacci ()
{
std:: uint64_t a = 0 , b = 1 ;
while ( true )
{
co_yield b;
auto tmp = a;
a = b;
b += tmp;
}
}
void usage ()
{
for ( auto i : fibonacci ())
{
if (i > 1'000'000 ) break ;
std::cout << i << std::endl;
}
} เมื่อฟังก์ชั่น coroutine ที่ส่งคืน generator<T> เรียกว่า coroutine ถูกสร้างขึ้นในขั้นต้น การดำเนินการของ coroutine เข้าสู่ร่างกาย coroutine เมื่อวิธี generator<T>::begin() ถูกเรียกและดำเนินการต่อไปจนกว่าจะถึงคำสั่ง co_yield ครั้งแรกหรือ Coroutine ทำงานให้เสร็จ
หากตัววนซ้ำที่ถูกส่งคืนไม่เท่ากับ end() ตัววนซ้ำจากนั้นการยกเลิกการประชุมตัววนซ้ำจะส่งคืนการอ้างอิงไปยังค่าที่ส่งผ่านไปยังคำสั่ง co_yield
การโทร operator++() บนตัววนซ้ำจะดำเนินการต่อการดำเนินการของ coroutine และดำเนินการต่อไปจนกว่าจะถึงจุด co_yield ถัดไปถึงจุดต่อไปหรือ Coroutine ทำงานให้เสร็จสมบูรณ์ ()
ข้อยกเว้นใด ๆ ที่ไม่มีการจัดการที่ถูกโยนโดย coroutine จะเผยแพร่ออกจาก begin() หรือ operator++() การโทรไปยังผู้โทร
สรุป API:
namespace cppcoro
{
template < typename T>
class generator
{
public:
using promise_type = <unspecified>;
class iterator
{
public:
using iterator_category = std::input_iterator_tag;
using value_type = std:: remove_reference_t <T>;
using reference = value_type&;
using pointer = value_type*;
using difference_type = std:: size_t ;
iterator ( const iterator& other) noexcept ;
iterator& operator =( const iterator& other) noexcept ;
// If the generator coroutine throws an unhandled exception before producing
// the next element then the exception will propagate out of this call.
iterator& operator ++();
reference operator *() const noexcept ;
pointer operator ->() const noexcept ;
bool operator ==( const iterator& other) const noexcept ;
bool operator !=( const iterator& other) const noexcept ;
};
// Constructs to the empty sequence.
generator () noexcept ;
generator (generator&& other) noexcept ;
generator& operator =(generator&& other) noexcept ;
generator ( const generator& other) = delete ;
generator& operator =( const generator&) = delete ;
~generator ();
// Starts executing the generator coroutine which runs until either a value is yielded
// or the coroutine runs to completion or an unhandled exception propagates out of the
// the coroutine.
iterator begin ();
iterator end () noexcept ;
// Swap the contents of two generators.
void swap (generator& other) noexcept ;
};
template < typename T>
void swap (generator<T>& a, generator<T>& b) noexcept ;
// Apply function, func, lazily to each element of the source generator
// and yield a sequence of the results of calls to func().
template < typename FUNC, typename T>
generator<std:: invoke_result_t <FUNC, T&>> fmap (FUNC func, generator<T> source);
}recursive_generator<T> recursive_generator นั้นคล้ายกับ generator ยกเว้นว่ามันถูกออกแบบมาเพื่อสนับสนุนอย่างมีประสิทธิภาพมากขึ้นเพื่อให้ได้องค์ประกอบของลำดับซ้อนกันเป็นองค์ประกอบของลำดับชั้นนอก
นอกเหนือจากความสามารถใน co_yield ค่าของประเภท T คุณยังสามารถ co_yield ค่าของ type recursive_generator<T>
เมื่อคุณ co_yield A recursive_generator<T> ค่าองค์ประกอบทั้งหมดของเครื่องกำเนิดไฟฟ้าที่ให้ผลจะได้รับเป็นองค์ประกอบของเครื่องกำเนิดไฟฟ้าปัจจุบัน coroutine ปัจจุบันจะถูกระงับจนกว่าผู้บริโภคจะใช้องค์ประกอบทั้งหมดของเครื่องกำเนิดไฟฟ้าซ้อนกันเสร็จหลังจากนั้นการดำเนินการจุดของ coroutine ปัจจุบันจะกลับมาดำเนินการต่อเพื่อสร้างองค์ประกอบถัดไป
ประโยชน์ของ recursive_generator<T> เหนือ generator<T> สำหรับการทำซ้ำโครงสร้างข้อมูลแบบเรียกซ้ำได้คือ iterator::operator++() สามารถกลับมาใช้ coroutine ได้โดยตรงเพื่อสร้างองค์ประกอบถัดไป ด้านล่างคือมีค่าใช้จ่ายเพิ่มเติม
ตัวอย่างเช่น:
// Lists the immediate contents of a directory.
cppcoro::generator<dir_entry> list_directory (std::filesystem::path path);
cppcoro::recursive_generator<dir_entry> list_directory_recursive (std::filesystem::path path)
{
for ( auto & entry : list_directory (path))
{
co_yield entry;
if (entry. is_directory ())
{
co_yield list_directory_recursive (entry. path ());
}
}
} โปรดทราบว่าการใช้ตัวดำเนินการ fmap() กับ recursive_generator<T> จะให้ประเภท generator<U> แทนที่จะเป็นแบบ recursive_generator<U> นี่เป็นเพราะการใช้ fmap โดยทั่วไปไม่ได้ใช้ในบริบทแบบเรียกซ้ำและเราพยายามหลีกเลี่ยงค่าใช้จ่ายเพิ่มเติมที่เกิดขึ้นจาก recursive_generator
async_generator<T> async_generator แสดงถึงประเภท coroutine ที่สร้างลำดับของค่าของประเภท, T โดยที่ค่าที่ผลิตอย่างเกียจคร้านและค่าอาจผลิตแบบอะซิงโครนัส
ร่างกาย Coroutine สามารถใช้ทั้ง co_await และ co_yield Expressions
ผู้บริโภคของเครื่องกำเนิดไฟฟ้าสามารถใช้ for co_await Range-based Loop เพื่อใช้ค่า
ตัวอย่าง
cppcoro::async_generator< int > ticker ( int count, threadpool& tp)
{
for ( int i = 0 ; i < count; ++i)
{
co_await tp. delay ( std::chrono::seconds ( 1 ));
co_yield i;
}
}
cppcoro::task<> consumer (threadpool& tp)
{
auto sequence = ticker ( 10 , tp);
for co_await (std:: uint32_t i : sequence)
{
std::cout << " Tick " << i << std::endl;
}
}สรุป API
// <cppcoro/async_generator.hpp>
namespace cppcoro
{
template < typename T>
class async_generator
{
public:
class iterator
{
public:
using iterator_tag = std::forward_iterator_tag;
using difference_type = std:: size_t ;
using value_type = std:: remove_reference_t <T>;
using reference = value_type&;
using pointer = value_type*;
iterator ( const iterator& other) noexcept ;
iterator& operator =( const iterator& other) noexcept ;
// Resumes the generator coroutine if suspended
// Returns an operation object that must be awaited to wait
// for the increment operation to complete.
// If the coroutine runs to completion then the iterator
// will subsequently become equal to the end() iterator.
// If the coroutine completes with an unhandled exception then
// that exception will be rethrown from the co_await expression.
Awaitable<iterator&> operator ++() noexcept ;
// Dereference the iterator.
pointer operator ->() const noexcept ;
reference operator *() const noexcept ;
bool operator ==( const iterator& other) const noexcept ;
bool operator !=( const iterator& other) const noexcept ;
};
// Construct to the empty sequence.
async_generator () noexcept ;
async_generator ( const async_generator&) = delete ;
async_generator (async_generator&& other) noexcept ;
~async_generator ();
async_generator& operator =( const async_generator&) = delete ;
async_generator& operator =(async_generator&& other) noexcept ;
void swap (async_generator& other) noexcept ;
// Starts execution of the coroutine and returns an operation object
// that must be awaited to wait for the first value to become available.
// The result of co_await'ing the returned object is an iterator that
// can be used to advance to subsequent elements of the sequence.
//
// This method is not valid to be called once the coroutine has
// run to completion.
Awaitable<iterator> begin () noexcept ;
iterator end () noexcept ;
};
template < typename T>
void swap (async_generator<T>& a, async_generator<T>& b);
// Apply 'func' to each element of the source generator, yielding a sequence of
// the results of calling 'func' on the source elements.
template < typename FUNC, typename T>
async_generator<std:: invoke_result_t <FUNC, T&>> fmap (FUNC func, async_generator<T> source);
} เมื่อวัตถุ async_generator ถูกทำลายมันจะขอยกเลิก coroutine พื้นฐาน หาก coroutine ทำงานเสร็จแล้วหรือถูกระงับในการแสดงออก co_yield แล้ว coroutine จะถูกทำลายทันที มิฉะนั้น coroutine จะดำเนินการต่อไปจนกว่าจะทำงานให้เสร็จสมบูรณ์หรือไปถึงนิพจน์ co_yield ถัดไป
เมื่อเฟรม coroutine ถูกทำลายตัวแปรของตัวแปรทั้งหมดในขอบเขต ณ จุดนั้นจะถูกดำเนินการเพื่อให้แน่ใจว่าทรัพยากรของเครื่องกำเนิดไฟฟ้าได้รับการทำความสะอาด
โปรดทราบว่าผู้โทรจะต้องตรวจสอบให้แน่ใจว่าวัตถุ async_generator จะต้องไม่ถูกทำลายในขณะที่ผู้บริโภค coroutine กำลังดำเนินการนิพจน์ co_await รอรายการถัดไปที่จะผลิต
single_consumer_eventนี่เป็นประเภทเหตุการณ์แบบแมนนวลที่ง่ายซึ่งรองรับ coroutine เพียงตัวเดียวที่รอคอยในแต่ละครั้ง ซึ่งสามารถใช้เพื่อ
สรุป API:
// <cppcoro/single_consumer_event.hpp>
namespace cppcoro
{
class single_consumer_event
{
public:
single_consumer_event ( bool initiallySet = false ) noexcept ;
bool is_set () const noexcept ;
void set ();
void reset () noexcept ;
Awaiter< void > operator co_await () const noexcept ;
};
}ตัวอย่าง:
# include < cppcoro/single_consumer_event.hpp >
cppcoro::single_consumer_event event;
std::string value;
cppcoro::task<> consumer ()
{
// Coroutine will suspend here until some thread calls event.set()
// eg. inside the producer() function below.
co_await event;
std::cout << value << std::endl;
}
void producer ()
{
value = " foo " ;
// This will resume the consumer() coroutine inside the call to set()
// if it is currently suspended.
event. set ();
}single_consumer_async_auto_reset_event คลาสนี้มีการซิงโครไนซ์แบบซิงโครไนซ์แบบ async ที่อนุญาตให้ coroutine เดี่ยวรอจนกว่าเหตุการณ์จะถูกส่งสัญญาณโดยการโทรไปยังวิธี set()
เมื่อ coroutine ที่กำลังรอเหตุการณ์จะถูกปล่อยออกมาโดยการเรียกก่อนหรือครั้งต่อไปเพื่อ set() เหตุการณ์จะถูกรีเซ็ตกลับไปยังสถานะ 'ไม่ตั้งค่า' โดยอัตโนมัติ
คลาสนี้เป็นรุ่นที่มีประสิทธิภาพมากขึ้นของ async_auto_reset_event ที่สามารถใช้ในกรณีที่มีเพียง coroutine เดียวเท่านั้นที่จะรอเหตุการณ์ในแต่ละครั้ง หากคุณต้องการสนับสนุน coroutines ที่รอคอยหลายครั้งพร้อมกันในเหตุการณ์ให้ใช้คลาส async_auto_reset_event แทน
สรุป API:
// <cppcoro/single_consumer_async_auto_reset_event.hpp>
namespace cppcoro
{
class single_consumer_async_auto_reset_event
{
public:
single_consumer_async_auto_reset_event (
bool initiallySet = false ) noexcept ;
// Change the event to the 'set' state. If a coroutine is awaiting the
// event then the event is immediately transitioned back to the 'not set'
// state and the coroutine is resumed.
void set () noexcept ;
// Returns an Awaitable type that can be awaited to wait until
// the event becomes 'set' via a call to the .set() method. If
// the event is already in the 'set' state then the coroutine
// continues without suspending.
// The event is automatically reset back to the 'not set' state
// before resuming the coroutine.
Awaiter< void > operator co_await () const noexcept ;
};
}ตัวอย่างการใช้งาน:
std::atomic< int > value;
cppcoro::single_consumer_async_auto_reset_event valueDecreasedEvent;
cppcoro::task<> wait_until_value_is_below ( int limit)
{
while (value. load (std::memory_order_relaxed) >= limit)
{
// Wait until there has been some change that we're interested in.
co_await valueDecreasedEvent;
}
}
void change_value ( int delta)
{
value. fetch_add (delta, std::memory_order_relaxed);
// Notify the waiter if there has been some change.
if (delta < 0 ) valueDecreasedEvent. set ();
}async_mutexจัดเตรียมสิ่งกีดขวางการแยกซึ่งกันและกันอย่างง่ายที่อนุญาตให้ผู้โทร 'co_await' mutex จากภายใน coroutine เพื่อระงับ coroutine จนกว่าจะได้รับการล็อค mutex
การใช้งานนั้นปราศจากการล็อคใน coroutine ที่รอ Mutex จะไม่ปิดกั้นเธรด แต่จะระงับ coroutine และต่อมากลับมาทำงานภายในการโทรเพื่อ unlock() โดยผู้ถือล็อคก่อนหน้า
สรุป API:
// <cppcoro/async_mutex.hpp>
namespace cppcoro
{
class async_mutex_lock ;
class async_mutex_lock_operation ;
class async_mutex_scoped_lock_operation ;
class async_mutex
{
public:
async_mutex () noexcept ;
~async_mutex ();
async_mutex ( const async_mutex&) = delete ;
async_mutex& operator ( const async_mutex&) = delete;
bool try_lock () noexcept ;
async_mutex_lock_operation lock_async () noexcept ;
async_mutex_scoped_lock_operation scoped_lock_async () noexcept ;
void unlock ();
};
class async_mutex_lock_operation
{
public:
bool await_ready () const noexcept ;
bool await_suspend (std::experimental::coroutine_handle<> awaiter) noexcept ;
void await_resume () const noexcept ;
};
class async_mutex_scoped_lock_operation
{
public:
bool await_ready () const noexcept ;
bool await_suspend (std::experimental::coroutine_handle<> awaiter) noexcept ;
[[nodiscard]] async_mutex_lock await_resume () const noexcept ;
};
class async_mutex_lock
{
public:
// Takes ownership of the lock.
async_mutex_lock (async_mutex& mutex, std:: adopt_lock_t ) noexcept ;
// Transfer ownership of the lock.
async_mutex_lock (async_mutex_lock&& other) noexcept ;
async_mutex_lock ( const async_mutex_lock&) = delete ;
async_mutex_lock& operator =( const async_mutex_lock&) = delete ;
// Releases the lock by calling unlock() on the mutex.
~async_mutex_lock ();
};
}ตัวอย่างการใช้งาน:
# include < cppcoro/async_mutex.hpp >
# include < cppcoro/task.hpp >
# include < set >
# include < string >
cppcoro::async_mutex mutex;
std::set<std::string> values;
cppcoro::task<> add_item (std::string value)
{
cppcoro::async_mutex_lock lock = co_await mutex. scoped_lock_async ();
values. insert ( std::move (value));
}async_manual_reset_event เหตุการณ์การรีเซ็ตด้วยตนเองคือ coroutine/thread-synchronization primitive ที่อนุญาตให้หนึ่งเธรดหรือมากกว่าสามารถรอจนกว่าเหตุการณ์จะถูกส่งสัญญาณโดยเธรดที่เรียกว่า set()
เหตุการณ์อยู่ในหนึ่งในสองรัฐ; 'ตั้งค่า' และ 'ไม่ตั้งค่า'
หากเหตุการณ์อยู่ในสถานะ 'set' เมื่อ coroutine รอเหตุการณ์ coroutine จะดำเนินต่อไปโดยไม่ระงับ อย่างไรก็ตามหาก coroutine อยู่ในสถานะ 'ไม่ได้ตั้งค่า' แล้ว coroutine จะถูกระงับจนกว่าจะมีเธรดบางอย่างในภายหลังเรียกวิธี set()
เธรดใด ๆ ที่ถูกระงับในขณะที่รอให้เหตุการณ์กลายเป็น 'ชุด' จะกลับมาทำงานต่อภายในการโทรครั้งต่อไปเพื่อ set() โดยเธรดบางอย่าง
โปรดทราบว่าคุณต้องตรวจสอบให้แน่ใจว่าไม่มี coroutines กำลังรอเหตุการณ์ 'ไม่ตั้งค่า' เมื่อเหตุการณ์ถูกทำลายเนื่องจากพวกเขาจะไม่กลับมาทำงานต่อ
ตัวอย่าง:
cppcoro::async_manual_reset_event event;
std::string value;
void producer ()
{
value = get_some_string_value ();
// Publish a value by setting the event.
event. set ();
}
// Can be called many times to create many tasks.
// All consumer tasks will wait until value has been published.
cppcoro::task<> consumer ()
{
// Wait until value has been published by awaiting event.
co_await event;
consume_value (value);
}สรุป API:
namespace cppcoro
{
class async_manual_reset_event_operation ;
class async_manual_reset_event
{
public:
async_manual_reset_event ( bool initiallySet = false ) noexcept ;
~async_manual_reset_event ();
async_manual_reset_event ( const async_manual_reset_event&) = delete ;
async_manual_reset_event (async_manual_reset_event&&) = delete ;
async_manual_reset_event& operator =( const async_manual_reset_event&) = delete ;
async_manual_reset_event& operator =(async_manual_reset_event&&) = delete ;
// Wait until the event becomes set.
async_manual_reset_event_operation operator co_await () const noexcept ;
bool is_set () const noexcept ;
void set () noexcept ;
void reset () noexcept ;
};
class async_manual_reset_event_operation
{
public:
async_manual_reset_event_operation (async_manual_reset_event& event) noexcept ;
bool await_ready () const noexcept ;
bool await_suspend (std::experimental::coroutine_handle<> awaiter) noexcept ;
void await_resume () const noexcept ;
};
}async_auto_reset_event เหตุการณ์ Auto-Reset คือ coroutine/thread-synchronization primitive ที่อนุญาตให้หนึ่งเธรดหรือมากกว่าสามารถรอจนกว่าเหตุการณ์จะถูกส่งสัญญาณโดยเธรดโดยการเรียก set()
เมื่อ coroutine ที่กำลังรอเหตุการณ์จะถูกปล่อยออกมาโดยการเรียกก่อนหรือครั้งต่อไปเพื่อ set() เหตุการณ์จะถูกรีเซ็ตกลับไปยังสถานะ 'ไม่ตั้งค่า' โดยอัตโนมัติ
สรุป API:
// <cppcoro/async_auto_reset_event.hpp>
namespace cppcoro
{
class async_auto_reset_event_operation ;
class async_auto_reset_event
{
public:
async_auto_reset_event ( bool initiallySet = false ) noexcept ;
~async_auto_reset_event ();
async_auto_reset_event ( const async_auto_reset_event&) = delete ;
async_auto_reset_event (async_auto_reset_event&&) = delete ;
async_auto_reset_event& operator =( const async_auto_reset_event&) = delete ;
async_auto_reset_event& operator =(async_auto_reset_event&&) = delete ;
// Wait for the event to enter the 'set' state.
//
// If the event is already 'set' then the event is set to the 'not set'
// state and the awaiting coroutine continues without suspending.
// Otherwise, the coroutine is suspended and later resumed when some
// thread calls 'set()'.
//
// Note that the coroutine may be resumed inside a call to 'set()'
// or inside another thread's call to 'operator co_await()'.
async_auto_reset_event_operation operator co_await () const noexcept ;
// Set the state of the event to 'set'.
//
// If there are pending coroutines awaiting the event then one
// pending coroutine is resumed and the state is immediately
// set back to the 'not set' state.
//
// This operation is a no-op if the event was already 'set'.
void set () noexcept ;
// Set the state of the event to 'not-set'.
//
// This is a no-op if the state was already 'not set'.
void reset () noexcept ;
};
class async_auto_reset_event_operation
{
public:
explicit async_auto_reset_event_operation (async_auto_reset_event& event) noexcept ;
async_auto_reset_event_operation ( const async_auto_reset_event_operation& other) noexcept ;
bool await_ready () const noexcept ;
bool await_suspend (std::experimental::coroutine_handle<> awaiter) noexcept ;
void await_resume () const noexcept ;
};
}async_latchสลักแบบ async เป็นแบบซิงโครไนซ์ดั้งเดิมที่ช่วยให้ coroutines สามารถรอแบบอะซิงโครนัสได้จนกว่าตัวนับจะลดลงเป็นศูนย์
สลักเป็นวัตถุแบบใช้ครั้งเดียว เมื่อเคาน์เตอร์มาถึงศูนย์สลักจะกลายเป็น 'พร้อม' และจะยังคงพร้อมจนกว่าสลักจะถูกทำลาย
สรุป API:
// <cppcoro/async_latch.hpp>
namespace cppcoro
{
class async_latch
{
public:
// Initialise the latch with the specified count.
async_latch (std:: ptrdiff_t initialCount) noexcept ;
// Query if the count has reached zero yet.
bool is_ready () const noexcept ;
// Decrement the count by n.
// This will resume any waiting coroutines if the count reaches zero
// as a result of this call.
// It is undefined behaviour to decrement the count below zero.
void count_down (std:: ptrdiff_t n = 1 ) noexcept ;
// Wait until the latch becomes ready.
// If the latch count is not yet zero then the awaiting coroutine will
// be suspended and later resumed by a call to count_down() that decrements
// the count to zero. If the latch count was already zero then the coroutine
// continues without suspending.
Awaiter< void > operator co_await () const noexcept ;
};
}sequence_barrier sequence_barrier เป็นแบบดั้งเดิมที่ซิงโครไนซ์ที่อนุญาตให้ผู้ผลิตรายเดียวและผู้บริโภคหลายรายประสานงานด้วยความเคารพต่อหมายเลขลำดับที่เพิ่มขึ้นแบบ monotonically
ผู้ผลิตรายเดียวก้าวหน้าหมายเลขลำดับโดยการเผยแพร่หมายเลขลำดับใหม่ในลำดับที่เพิ่มขึ้นแบบ monotonically ผู้บริโภคหนึ่งรายขึ้นไปสามารถสอบถามหมายเลขลำดับที่เผยแพร่ล่าสุดและสามารถรอได้จนกว่าจะมีการเผยแพร่หมายเลขลำดับที่เฉพาะเจาะจง
สามารถใช้สิ่งกีดขวางลำดับเพื่อเป็นตัวแทนของเคอร์เซอร์ลงในผู้ผลิตที่ปลอดภัยจากด้ายที่ปลอดภัย
ดูรูปแบบ lmax disruptor สำหรับพื้นหลังเพิ่มเติม: https://lmax-exchange.github.io/disruptor/files/disruptor-1.0.pdf
บทสรุป API:
namespace cppcoro
{
template < typename SEQUENCE = std:: size_t ,
typename TRAITS = sequence_traits<SEQUENCE>>
class sequence_barrier
{
public:
sequence_barrier (SEQUENCE initialSequence = TRAITS::initial_sequence) noexcept ;
~sequence_barrier ();
SEQUENCE last_published () const noexcept ;
// Wait until the specified targetSequence number has been published.
//
// If the operation does not complete synchronously then the awaiting
// coroutine is resumed on the specified scheduler. Otherwise, the
// coroutine continues without suspending.
//
// The co_await expression resumes with the updated last_published()
// value, which is guaranteed to be at least 'targetSequence'.
template < typename SCHEDULER>
[[nodiscard]]
Awaitable<SEQUENCE> wait_until_published (SEQUENCE targetSequence,
SCHEDULER& scheduler) const noexcept ;
void publish (SEQUENCE sequence) noexcept ;
};
}single_producer_sequencer single_producer_sequencer เป็น primitive ซิงโครไนซ์ที่สามารถใช้เพื่อประสานการเข้าถึงวงแหวนบัฟเฟอร์สำหรับผู้ผลิตรายเดียวและผู้บริโภคหนึ่งคนขึ้นไป
ผู้ผลิตคนแรกได้รับหนึ่งช่องขึ้นไปในวงแหวนบัฟเฟอร์เขียนไปยังองค์ประกอบวงแหวนบัฟเฟอร์ที่สอดคล้องกับสล็อตเหล่านั้นและในที่สุดก็เผยแพร่ค่าที่เขียนลงในสล็อตเหล่านั้น ผู้ผลิตไม่สามารถผลิตองค์ประกอบมากกว่า 'บัฟเฟอร์' ล่วงหน้าก่อนที่ผู้บริโภคจะบริโภค
จากนั้นผู้บริโภคจะรอองค์ประกอบบางอย่างที่จะเผยแพร่ประมวลผลรายการแล้วแจ้งให้ผู้ผลิตทราบเมื่อมีการประมวลผลรายการเสร็จสิ้นโดยการเผยแพร่หมายเลขลำดับที่เสร็จสิ้นการบริโภคในวัตถุ sequence_barrier
บทสรุป API:
// <cppcoro/single_producer_sequencer.hpp>
namespace cppcoro
{
template <
typename SEQUENCE = std:: size_t ,
typename TRAITS = sequence_traits<SEQUENCE>>
class single_producer_sequencer
{
public:
using size_type = typename sequence_range<SEQUENCE, TRAITS>::size_type;
single_producer_sequencer (
const sequence_barrier<SEQUENCE, TRAITS>& consumerBarrier,
std:: size_t bufferSize,
SEQUENCE initialSequence = TRAITS::initial_sequence) noexcept ;
// Publisher API:
template < typename SCHEDULER>
[[nodiscard]]
Awaitable<SEQUENCE> claim_one (SCHEDULER& scheduler) noexcept ;
template < typename SCHEDULER>
[[nodiscard]]
Awaitable<sequence_range<SEQUENCE>> claim_up_to (
std:: size_t count,
SCHEDULER& scheduler) noexcept ;
void publish (SEQUENCE sequence) noexcept ;
// Consumer API:
SEQUENCE last_published () const noexcept ;
template < typename SCHEDULER>
[[nodiscard]]
Awaitable<SEQUENCE> wait_until_published (
SEQUENCE targetSequence,
SCHEDULER& scheduler) const noexcept ;
};
}ตัวอย่างการใช้งาน:
using namespace cppcoro ;
using namespace std ::chrono ;
struct message
{
int id;
steady_clock::time_point timestamp;
float data;
};
constexpr size_t bufferSize = 16384 ; // Must be power-of-two
constexpr size_t indexMask = bufferSize - 1 ;
message buffer[bufferSize];
task< void > producer (
io_service& ioSvc,
single_producer_sequencer< size_t >& sequencer)
{
auto start = steady_clock::now ();
for ( int i = 0 ; i < 1'000'000 ; ++i)
{
// Wait until a slot is free in the buffer.
size_t seq = co_await sequencer. claim_one (ioSvc);
// Populate the message.
auto & msg = buffer[seq & indexMask];
msg. id = i;
msg. timestamp = steady_clock::now ();
msg. data = 123 ;
// Publish the message.
sequencer. publish (seq);
}
// Publish a sentinel
auto seq = co_await sequencer. claim_one (ioSvc);
auto & msg = buffer[seq & indexMask];
msg. id = - 1 ;
sequencer. publish (seq);
}
task< void > consumer (
static_thread_pool& threadPool,
const single_producer_sequencer< size_t >& sequencer,
sequence_barrier< size_t >& consumerBarrier)
{
size_t nextToRead = 0 ;
while ( true )
{
// Wait until the next message is available
// There may be more than one available.
const size_t available = co_await sequencer. wait_until_published (nextToRead, threadPool);
do {
auto & msg = buffer[nextToRead & indexMask];
if (msg. id == - 1 )
{
consumerBarrier. publish (nextToRead);
co_return ;
}
processMessage (msg);
} while (nextToRead++ != available);
// Notify the producer that we've finished processing
// up to 'nextToRead - 1'.
consumerBarrier. publish (available);
}
}
task< void > example (io_service& ioSvc, static_thread_pool& threadPool)
{
sequence_barrier< size_t > barrier;
single_producer_sequencer< size_t > sequencer{barrier, bufferSize};
co_await when_all (
producer (tp, sequencer),
consumer (tp, sequencer, barrier));
}multi_producer_sequencer คลาส multi_producer_sequencer เป็นแบบซิงโครไนซ์ดั้งเดิมที่ประสานการเข้าถึงวงแหวนบัฟเฟอร์สำหรับผู้ผลิตหลายรายและผู้บริโภคหนึ่งคนขึ้นไป
สำหรับตัวแปรผู้ผลิตเดี่ยวโปรดดูคลาส single_producer_sequencer
โปรดทราบว่าวงแหวนบัฟเฟอร์ต้องมีขนาดที่เป็นพลังของสอง นี่เป็นเพราะการใช้งานใช้ bitmasks แทนการแบ่งจำนวนเต็ม/โมดูโลเพื่อคำนวณการชดเชยลงในบัฟเฟอร์ นอกจากนี้ยังช่วยให้หมายเลขลำดับสามารถห่อรอบค่า 32 บิต/64 บิตได้อย่างปลอดภัย
สรุป API:
// <cppcoro/multi_producer_sequencer.hpp>
namespace cppcoro
{
template < typename SEQUENCE = std:: size_t ,
typename TRAITS = sequence_traits<SEQUENCE>>
class multi_producer_sequencer
{
public:
multi_producer_sequencer (
const sequence_barrier<SEQUENCE, TRAITS>& consumerBarrier,
SEQUENCE initialSequence = TRAITS::initial_sequence);
std:: size_t buffer_size () const noexcept ;
// Consumer interface
//
// Each consumer keeps track of their own 'lastKnownPublished' value
// and must pass this to the methods that query for an updated last-known
// published sequence number.
SEQUENCE last_published_after (SEQUENCE lastKnownPublished) const noexcept ;
template < typename SCHEDULER>
Awaitable<SEQUENCE> wait_until_published (
SEQUENCE targetSequence,
SEQUENCE lastKnownPublished,
SCHEDULER& scheduler) const noexcept ;
// Producer interface
// Query whether any slots available for claiming (approx.)
bool any_available () const noexcept ;
template < typename SCHEDULER>
Awaitable<SEQUENCE> claim_one (SCHEDULER& scheduler) noexcept ;
template < typename SCHEDULER>
Awaitable<sequence_range<SEQUENCE, TRAITS>> claim_up_to (
std:: size_t count,
SCHEDULER& scheduler) noexcept ;
// Mark the specified sequence number as published.
void publish (SEQUENCE sequence) noexcept ;
// Mark all sequence numbers in the specified range as published.
void publish ( const sequence_range<SEQUENCE, TRAITS>& range) noexcept ;
};
} cancellation_token เป็นค่าที่สามารถส่งผ่านไปยังฟังก์ชั่นที่อนุญาตให้ผู้โทรสามารถสื่อสารคำขอเพื่อยกเลิกการดำเนินการกับฟังก์ชั่นนั้นได้ในภายหลัง
ในการรับ cancellation_token ที่สามารถยกเลิกได้คุณต้องสร้างวัตถุ cancellation_source ก่อน วิธี cancellation_source::token() สามารถใช้ในการผลิตค่า cancellation_token ใหม่ที่เชื่อมโยงกับวัตถุ cancellation_source นั้น
เมื่อคุณต้องการยกเลิกการร้องขอการดำเนินการในภายหลังคุณได้ผ่านการ cancellation_token ให้คุณสามารถเรียก cancellation_source::request_cancellation() บนวัตถุ cancellation_source ที่เกี่ยวข้อง
ฟังก์ชั่นสามารถตอบสนองต่อการร้องขอการยกเลิกในหนึ่งในสองวิธี:
cancellation_token::is_cancellation_requested() หรือ cancellation_token::throw_if_cancellation_requested()cancellation_registrationสรุป API:
namespace cppcoro
{
class cancellation_source
{
public:
// Construct a new, independently cancellable cancellation source.
cancellation_source ();
// Construct a new reference to the same cancellation state.
cancellation_source ( const cancellation_source& other) noexcept ;
cancellation_source (cancellation_source&& other) noexcept ;
~cancellation_source ();
cancellation_source& operator =( const cancellation_source& other) noexcept ;
cancellation_source& operator =(cancellation_source&& other) noexcept ;
bool is_cancellation_requested () const noexcept ;
bool can_be_cancelled () const noexcept ;
void request_cancellation ();
cancellation_token token () const noexcept ;
};
class cancellation_token
{
public:
// Construct a token that can't be cancelled.
cancellation_token () noexcept ;
cancellation_token ( const cancellation_token& other) noexcept ;
cancellation_token (cancellation_token&& other) noexcept ;
~cancellation_token ();
cancellation_token& operator =( const cancellation_token& other) noexcept ;
cancellation_token& operator =(cancellation_token&& other) noexcept ;
bool is_cancellation_requested () const noexcept ;
void throw_if_cancellation_requested () const ;
// Query if this token can ever have cancellation requested.
// Code can use this to take a more efficient code-path in cases
// that the operation does not need to handle cancellation.
bool can_be_cancelled () const noexcept ;
};
// RAII class for registering a callback to be executed if cancellation
// is requested on a particular cancellation token.
class cancellation_registration
{
public:
// Register a callback to be executed if cancellation is requested.
// Callback will be called with no arguments on the thread that calls
// request_cancellation() if cancellation is not yet requested, or
// called immediately if cancellation has already been requested.
// Callback must not throw an unhandled exception when called.
template < typename CALLBACK>
cancellation_registration (cancellation_token token, CALLBACK&& callback);
cancellation_registration ( const cancellation_registration& other) = delete ;
~cancellation_registration ();
};
class operation_cancelled : public std :: exception
{
public:
operation_cancelled ();
const char * what () const override ;
};
}ตัวอย่าง: วิธีการเลือกตั้ง
cppcoro::task<> do_something_async (cppcoro::cancellation_token token)
{
// Explicitly define cancellation points within the function
// by calling throw_if_cancellation_requested().
token. throw_if_cancellation_requested ();
co_await do_step_1 ();
token. throw_if_cancellation_requested ();
do_step_2 ();
// Alternatively, you can query if cancellation has been
// requested to allow yourself to do some cleanup before
// returning.
if (token. is_cancellation_requested ())
{
display_message_to_user ( " Cancelling operation... " );
do_cleanup ();
throw cppcoro::operation_cancelled{};
}
do_final_step ();
}ตัวอย่าง: วิธีการโทรกลับ
// Say we already have a timer abstraction that supports being
// cancelled but it doesn't support cancellation_tokens natively.
// You can use a cancellation_registration to register a callback
// that calls the existing cancellation API. e.g.
cppcoro::task<> cancellable_timer_wait (cppcoro::cancellation_token token)
{
auto timer = create_timer (10s);
cppcoro::cancellation_registration registration (token, [&]
{
// Call existing timer cancellation API.
timer. cancel ();
});
co_await timer;
}static_thread_pool คลาส static_thread_pool ให้เป็นนามธรรมที่ให้คุณกำหนดเวลาทำงานในกลุ่มขนาดคงที่ของเธรด
คลาสนี้ใช้แนวคิด Scheduler (ดูด้านล่าง)
คุณสามารถ enqueue ทำงานกับพูลเธรดได้โดยดำเนินการ co_await threadPool.schedule() การดำเนินการนี้จะระงับ coroutine ปัจจุบัน enqueue มันสำหรับการดำเนินการบนเกลียวพูลและพูลเธรดจะกลับมาใช้ coroutine ต่อเมื่อเธรดในเธรดพูลอยู่ข้างหน้าฟรีเพื่อเรียกใช้ coroutine การดำเนินการนี้รับประกันว่าจะไม่โยนและในกรณีทั่วไปจะไม่จัดสรรหน่วยความจำใด ๆ
ชั้นเรียนนี้ใช้ประโยชน์จากอัลกอริทึมการขโมยงานเพื่อโหลดสมดุลในหลาย ๆ เธรด งานที่ถูกนำไปใช้กับพูลเธรดจากเธรดเธรดพูลจะถูกกำหนดไว้สำหรับการดำเนินการในเธรดเดียวกันในคิว Lifo งานที่ถูกนำไปใช้กับพวงเธรดจากเธรดระยะไกลจะถูกนำไปใช้กับคิวฟีฟวทั่วโลก เมื่อด้ายของคนงานหมดจากการทำงานจากคิวท้องถิ่นมันพยายามที่จะ dequeue ทำงานจากคิวทั่วโลก หากคิวนั้นว่างเปล่าแล้วมันก็พยายามขโมยงานจากด้านหลังของคิวของกระทู้คนงานคนอื่น ๆ
สรุป API:
namespace cppcoro
{
class static_thread_pool
{
public:
// Initialise the thread-pool with a number of threads equal to
// std::thread::hardware_concurrency().
static_thread_pool ();
// Initialise the thread pool with the specified number of threads.
explicit static_thread_pool (std:: uint32_t threadCount);
std:: uint32_t thread_count () const noexcept ;
class schedule_operation
{
public:
schedule_operation (static_thread_pool* tp) noexcept ;
bool await_ready () noexcept ;
bool await_suspend (std::experimental::coroutine_handle<> h) noexcept ;
bool await_resume () noexcept ;
private:
// unspecified
};
// Return an operation that can be awaited by a coroutine.
//
//
[[nodiscard]]
schedule_operation schedule () noexcept ;
private:
// Unspecified
};
}ตัวอย่างการใช้งาน: ง่าย
cppcoro::task<std::string> do_something_on_threadpool (cppcoro::static_thread_pool& tp)
{
// First schedule the coroutine onto the threadpool.
co_await tp. schedule ();
// When it resumes, this coroutine is now running on the threadpool.
do_something ();
} ตัวอย่างการใช้งาน: ทำสิ่งต่าง ๆ ในแบบขนาน - ใช้ตัวดำเนิน schedule_on() ด้วย static_thread_pool
cppcoro::task< double > dot_product (static_thread_pool& tp, double a[], double b[], size_t count)
{
if (count > 1000 )
{
// Subdivide the work recursively into two equal tasks
// The first half is scheduled to the thread pool so it can run concurrently
// with the second half which continues on this thread.
size_t halfCount = count / 2 ;
auto [first, second] = co_await when_all (
schedule_on (tp, dot_product (tp, a, b, halfCount),
dot_product (tp, a + halfCount, b + halfCount, count - halfCount));
co_return first + second;
}
else
{
double sum = 0.0 ;
for ( size_t i = 0 ; i < count; ++i)
{
sum += a[i] * b[i];
}
co_return sum;
}
}io_service และ io_work_scope คลาส io_service ให้เป็นนามธรรมสำหรับการประมวลผลเหตุการณ์ I/O ที่เสร็จสมบูรณ์จากการดำเนินการ I/O แบบอะซิงโครนัส
เมื่อการดำเนินการ I/O แบบอะซิงโครนัสเสร็จสิ้น coroutine ที่รอการดำเนินการนั้นจะกลับมาทำงานต่อบนเธรด I/O ภายในการโทรไปยังหนึ่งในวิธีการประมวลผลเหตุการณ์: process_events() , process_pending_events() , process_one_event() หรือ process_one_pending_event()
คลาส io_service ไม่ได้จัดการเธรด I/O ใด ๆ คุณต้องตรวจสอบให้แน่ใจว่าเธรดบางอย่างเรียกวิธีการประมวลผลเหตุการณ์อย่างใดอย่างหนึ่งสำหรับ coroutines ที่รอการจัดส่งเหตุการณ์ I/O ให้เสร็จสิ้น นี่อาจเป็นเธรดเฉพาะที่เรียกว่า process_events() หรือผสมกับลูปเหตุการณ์อื่น ๆ (เช่นลูปเหตุการณ์ UI) โดยการสำรวจเหตุการณ์ใหม่เป็นระยะ ๆ ผ่านการโทรไปยัง process_pending_events() หรือ process_one_pending_event()
สิ่งนี้ช่วยให้การรวมกลุ่มเหตุการณ์ io_service กับลูปเหตุการณ์อื่น ๆ เช่นการวนรอบเหตุการณ์ผู้ใช้อินเทอร์เฟซ
คุณสามารถประมวลผลกิจกรรมหลาย ๆ อย่างในหลายเธรดโดยมีหลายเธรดการโทร process_events() คุณสามารถระบุคำใบ้เกี่ยวกับจำนวนเธรดสูงสุดที่จะมีการประมวลผลเหตุการณ์อย่างแข็งขันผ่านพารามิเตอร์ io_service Constructor เสริม
บน Windows การใช้งานใช้ประโยชน์จากพอร์ตพอร์ต Windows I/O ที่เสร็จสมบูรณ์เพื่อส่งเหตุการณ์ไปยังเธรด I/O ในลักษณะที่ปรับขนาดได้
สรุป API:
namespace cppcoro
{
class io_service
{
public:
class schedule_operation ;
class timed_schedule_operation ;
io_service ();
io_service (std:: uint32_t concurrencyHint);
io_service (io_service&&) = delete ;
io_service ( const io_service&) = delete ;
io_service& operator =(io_service&&) = delete ;
io_service& operator =( const io_service&) = delete ;
~io_service ();
// Scheduler methods
[[nodiscard]]
schedule_operation schedule () noexcept ;
template < typename REP, typename RATIO>
[[nodiscard]]
timed_schedule_operation schedule_after (
std::chrono::duration<REP, RATIO> delay,
cppcoro::cancellation_token cancellationToken = {}) noexcept ;
// Event-loop methods
//
// I/O threads must call these to process I/O events and execute
// scheduled coroutines.
std:: uint64_t process_events ();
std:: uint64_t process_pending_events ();
std:: uint64_t process_one_event ();
std:: uint64_t process_one_pending_event ();
// Request that all threads processing events exit their event loops.
void stop () noexcept ;
// Query if some thread has called stop()
bool is_stop_requested () const noexcept ;
// Reset the event-loop after a call to stop() so that threads can
// start processing events again.
void reset ();
// Reference-counting methods for tracking outstanding references
// to the io_service.
//
// The io_service::stop() method will be called when the last work
// reference is decremented.
//
// Use the io_work_scope RAII class to manage calling these methods on
// entry-to and exit-from a scope.
void notify_work_started () noexcept ;
void notify_work_finished () noexcept ;
};
class io_service ::schedule_operation
{
public:
schedule_operation ( const schedule_operation&) noexcept ;
schedule_operation& operator =( const schedule_operation&) noexcept ;
bool await_ready () const noexcept ;
void await_suspend (std::experimental::coroutine_handle<> awaiter) noexcept ;
void await_resume () noexcept ;
};
class io_service ::timed_schedule_operation
{
public:
timed_schedule_operation (timed_schedule_operation&&) noexcept ;
timed_schedule_operation ( const timed_schedule_operation&) = delete ;
timed_schedule_operation& operator =( const timed_schedule_operation&) = delete ;
timed_schedule_operation& operator =(timed_schedule_operation&&) = delete ;
bool await_ready () const noexcept ;
void await_suspend (std::experimental::coroutine_handle<> awaiter);
void await_resume ();
};
class io_work_scope
{
public:
io_work_scope (io_service& ioService) noexcept ;
io_work_scope ( const io_work_scope& other) noexcept ;
io_work_scope (io_work_scope&& other) noexcept ;
~io_work_scope ();
io_work_scope& operator =( const io_work_scope& other) noexcept ;
io_work_scope& operator =(io_work_scope&& other) noexcept ;
io_service& service () const noexcept ;
};
}ตัวอย่าง:
# include < cppcoro/task.hpp >
# include < cppcoro/task.hpp >
# include < cppcoro/io_service.hpp >
# include < cppcoro/read_only_file.hpp >
# include < experimental/filesystem >
# include < memory >
# include < algorithm >
# include < iostream >
namespace fs = std::experimental::filesystem;
cppcoro::task<std:: uint64_t > count_lines (cppcoro::io_service& ioService, fs::path path)
{
auto file = cppcoro::read_only_file::open (ioService, path);
constexpr size_t bufferSize = 4096 ;
auto buffer = std::make_unique<std:: uint8_t []>(bufferSize);
std:: uint64_t newlineCount = 0 ;
for (std:: uint64_t offset = 0 , fileSize = file. size (); offset < fileSize;)
{
const auto bytesToRead = static_cast < size_t >(
std::min<std:: uint64_t >(bufferSize, fileSize - offset));
const auto bytesRead = co_await file. read (offset, buffer. get (), bytesToRead);
newlineCount += std::count (buffer. get (), buffer. get () + bytesRead, ' n ' );
offset += bytesRead;
}
co_return newlineCount;
}
cppcoro::task<> run (cppcoro::io_service& ioService)
{
cppcoro::io_work_scope ioScope (ioService);
auto lineCount = co_await count_lines (ioService, fs::path{ " foo.txt " });
std::cout << " foo.txt has " << lineCount << " lines. " << std::endl;;
}
cppcoro::task<> process_events (cppcoro::io_service& ioService)
{
// Process events until the io_service is stopped.
// ie. when the last io_work_scope goes out of scope.
ioService. process_events ();
co_return ;
}
int main ()
{
cppcoro::io_service ioService;
cppcoro::sync_wait ( cppcoro::when_all_ready (
run (ioService),
process_events (ioService)));
return 0 ;
}io_service เป็นตัวกำหนดตารางเวลา คลาส io_service ใช้อินเทอร์เฟซสำหรับแนวคิดของ Scheduler และแนวคิด DelayedScheduler
สิ่งนี้ช่วยให้ coroutine สามารถระงับการดำเนินการในเธรดปัจจุบันและกำหนดเวลาเองสำหรับการเริ่มต้นใหม่ในเธรด I/O ที่เกี่ยวข้องกับวัตถุ io_service เฉพาะ
ตัวอย่าง:
cppcoro::task<> do_something (cppcoro::io_service& ioService)
{
// Coroutine starts execution on the thread of the task awaiter.
// A coroutine can transfer execution to an I/O thread by awaiting the
// result of io_service::schedule().
co_await ioService. schedule ();
// At this point, the coroutine is now executing on an I/O thread
// inside a call to one of the io_service event processing methods.
// A coroutine can also perform a delayed-schedule that will suspend
// the coroutine for a specified duration of time before scheduling
// it for resumption on an I/O thread.
co_await ioService. schedule_after (100ms);
// At this point, the coroutine is executing on a potentially different I/O thread.
}file , readable_file , writable_fileประเภทเหล่านี้เป็นคลาสพื้นฐานที่เป็นนามธรรมสำหรับการดำเนินการไฟล์คอนกรีต I/O
สรุป API:
namespace cppcoro
{
class file_read_operation ;
class file_write_operation ;
class file
{
public:
virtual ~file ();
std:: uint64_t size () const ;
protected:
file (file&& other) noexcept ;
};
class readable_file : public virtual file
{
public:
[[nodiscard]]
file_read_operation read (
std:: uint64_t offset,
void * buffer,
std:: size_t byteCount,
cancellation_token ct = {}) const noexcept ;
};
class writable_file : public virtual file
{
public:
void set_size (std:: uint64_t fileSize);
[[nodiscard]]
file_write_operation write (
std:: uint64_t offset,
const void * buffer,
std:: size_t byteCount,
cancellation_token ct = {}) noexcept ;
};
class file_read_operation
{
public:
file_read_operation (file_read_operation&& other) noexcept ;
bool await_ready () const noexcept ;
bool await_suspend (std::experimental::coroutine_handle<> awaiter);
std:: size_t await_resume ();
};
class file_write_operation
{
public:
file_write_operation (file_write_operation&& other) noexcept ;
bool await_ready () const noexcept ;
bool await_suspend (std::experimental::coroutine_handle<> awaiter);
std:: size_t await_resume ();
};
}read_only_file , write_only_file , read_write_fileประเภทเหล่านี้เป็นตัวแทนของคลาสคอนกรีต I/O
สรุป API:
namespace cppcoro
{
class read_only_file : public readable_file
{
public:
[[nodiscard]]
static read_only_file open (
io_service& ioService,
const std::experimental::filesystem::path& path,
file_share_mode shareMode = file_share_mode::read,
file_buffering_mode bufferingMode = file_buffering_mode::default_);
};
class write_only_file : public writable_file
{
public:
[[nodiscard]]
static write_only_file open (
io_service& ioService,
const std::experimental::filesystem::path& path,
file_open_mode openMode = file_open_mode::create_or_open,
file_share_mode shareMode = file_share_mode::none,
file_buffering_mode bufferingMode = file_buffering_mode::default_);
};
class read_write_file : public readable_file , public writable_file
{
public:
[[nodiscard]]
static read_write_file open (
io_service& ioService,
const std::experimental::filesystem::path& path,
file_open_mode openMode = file_open_mode::create_or_open,
file_share_mode shareMode = file_share_mode::none,
file_buffering_mode bufferingMode = file_buffering_mode::default_);
};
} ฟังก์ชั่น open() ทั้งหมดโยน std::system_error เมื่อล้มเหลว
หมายเหตุ: การสร้างเครือข่าย abstractions ได้รับการสนับสนุนในแพลตฟอร์ม Windows เท่านั้น การสนับสนุน Linux จะมาเร็ว ๆ นี้
socketคลาสซ็อกเก็ตสามารถใช้ในการส่ง/รับข้อมูลผ่านเครือข่ายแบบอะซิงโครนัส
ปัจจุบันรองรับ TCP/IP, UDP/IP ผ่าน IPv4 และ IPv6 เท่านั้น
สรุป API:
// <cppcoro/net/socket.hpp>
namespace cppcoro ::net
{
class socket
{
public:
static socket create_tcpv4 (ip_service& ioSvc);
static socket create_tcpv6 (ip_service& ioSvc);
static socket create_updv4 (ip_service& ioSvc);
static socket create_udpv6 (ip_service& ioSvc);
socket (socket&& other) noexcept ;
~socket ();
socket& operator =(socket&& other) noexcept ;
// Return the native socket handle for the socket
<platform-specific> native_handle () noexcept ;
const ip_endpoint& local_endpoint () const noexcept ;
const ip_endpoint& remote_endpoint () const noexcept ;
void bind ( const ip_endpoint& localEndPoint);
void listen ();
[[nodiscard]]
Awaitable< void > connect ( const ip_endpoint& remoteEndPoint) noexcept ;
[[nodiscard]]
Awaitable< void > connect ( const ip_endpoint& remoteEndPoint,
cancellation_token ct) noexcept ;
[[nodiscard]]
Awaitable< void > accept (socket& acceptingSocket) noexcept ;
[[nodiscard]]
Awaitable< void > accept (socket& acceptingSocket,
cancellation_token ct) noexcept ;
[[nodiscard]]
Awaitable< void > disconnect () noexcept ;
[[nodiscard]]
Awaitable< void > disconnect (cancellation_token ct) noexcept ;
[[nodiscard]]
Awaitable<std:: size_t > send ( const void * buffer, std:: size_t size) noexcept ;
[[nodiscard]]
Awaitable<std:: size_t > send ( const void * buffer,
std:: size_t size,
cancellation_token ct) noexcept ;
[[nodiscard]]
Awaitable<std:: size_t > recv ( void * buffer, std:: size_t size) noexcept ;
[[nodiscard]]
Awaitable<std:: size_t > recv ( void * buffer,
std:: size_t size,
cancellation_token ct) noexcept ;
[[nodiscard]]
socket_recv_from_operation recv_from (
void * buffer,
std:: size_t size) noexcept ;
[[nodiscard]]
socket_recv_from_operation_cancellable recv_from (
void * buffer,
std:: size_t size,
cancellation_token ct) noexcept ;
[[nodiscard]]
socket_send_to_operation send_to (
const ip_endpoint& destination,
const void * buffer,
std:: size_t size) noexcept ;
[[nodiscard]]
socket_send_to_operation_cancellable send_to (
const ip_endpoint& destination,
const void * buffer,
std:: size_t size,
cancellation_token ct) noexcept ;
void close_send ();
void close_recv ();
};
}ตัวอย่าง: Echo Server
# include < cppcoro/net/socket.hpp >
# include < cppcoro/io_service.hpp >
# include < cppcoro/cancellation_source.hpp >
# include < cppcoro/async_scope.hpp >
# include < cppcoro/on_scope_exit.hpp >
# include < memory >
# include < iostream >
cppcoro::task< void > handle_connection (socket s)
{
try
{
const size_t bufferSize = 16384 ;
auto buffer = std::make_unique< unsigned char []>(bufferSize);
size_t bytesRead;
do {
// Read some bytes
bytesRead = co_await s. recv (buffer. get (), bufferSize);
// Write some bytes
size_t bytesWritten = 0 ;
while (bytesWritten < bytesRead) {
bytesWritten += co_await s. send (
buffer. get () + bytesWritten,
bytesRead - bytesWritten);
}
} while (bytesRead != 0 );
s. close_send ();
co_await s. disconnect ();
}
catch (...)
{
std::cout << " connection failed " << std::
}
}
cppcoro::task< void > echo_server (
cppcoro::net::ipv4_endpoint endpoint,
cppcoro::io_service& ioSvc,
cancellation_token ct)
{
cppcoro::async_scope scope;
std::exception_ptr ex;
try
{
auto listeningSocket = cppcoro::net::socket::create_tcpv4 (ioSvc);
listeningSocket. bind (endpoint);
listeningSocket. listen ();
while ( true ) {
auto connection = cppcoro::net::socket::create_tcpv4 (ioSvc);
co_await listeningSocket. accept (connection, ct);
scope. spawn ( handle_connection ( std::move (connection)));
}
}
catch (cppcoro::operation_cancelled)
{
}
catch (...)
{
ex = std::current_exception ();
}
// Wait until all handle_connection tasks have finished.
co_await scope. join ();
if (ex) std::rethrow_exception (ex);
}
int main ( int argc, const char * argv[])
{
cppcoro::io_service ioSvc;
if (argc != 2 ) return - 1 ;
auto endpoint = cppcoro::ipv4_endpoint::from_string (argv[ 1 ]);
if (!endpoint) return - 1 ;
( void ) cppcoro::sync_wait ( cppcoro::when_all (
[&]() -> task<>
{
// Shutdown the event loop once finished.
auto stopOnExit = cppcoro::on_scope_exit ([&] { ioSvc. stop (); });
cppcoro::cancellation_source canceller;
co_await cppcoro::when_all (
[&]() -> task<>
{
// Run for 30s then stop accepting new connections.
co_await ioSvc. schedule_after ( std::chrono::seconds ( 30 ));
canceller. request_cancellation ();
}(),
echo_server (*endpoint, ioSvc, canceller. token ()));
}(),
[&]() -> task<>
{
ioSvc. process_events ();
}()));
return 0 ;
}ip_address , ipv4_address , ipv6_addressคลาสผู้ช่วยสำหรับเป็นตัวแทนที่อยู่ IP
บทสรุป API:
namespace cppcoro ::net
{
class ipv4_address
{
using bytes_t = std:: uint8_t [ 4 ];
public:
constexpr ipv4_address ();
explicit constexpr ipv4_address (std:: uint32_t integer);
explicit constexpr ipv4_address ( const std::uint8_t (&bytes)[4]);
explicit constexpr ipv4_address (std:: uint8_t b0,
std:: uint8_t b1,
std:: uint8_t b2,
std:: uint8_t b3);
constexpr const bytes_t & bytes () const ;
constexpr std:: uint32_t to_integer () const ;
static constexpr ipv4_address loopback ();
constexpr bool is_loopback () const ;
constexpr bool is_private_network () const ;
constexpr bool operator ==(ipv4_address other) const ;
constexpr bool operator !=(ipv4_address other) const ;
constexpr bool operator <(ipv4_address other) const ;
constexpr bool operator >(ipv4_address other) const ;
constexpr bool operator <=(ipv4_address other) const ;
constexpr bool operator >=(ipv4_address other) const ;
std::string to_string ();
static std::optional<ipv4_address> from_string (std::string_view string) noexcept ;
};
class ipv6_address
{
using bytes_t = std:: uint8_t [ 16 ];
public:
constexpr ipv6_address ();
explicit constexpr ipv6_address (
std:: uint64_t subnetPrefix,
std:: uint64_t interfaceIdentifier);
constexpr ipv6_address (
std:: uint16_t part0,
std:: uint16_t part1,
std:: uint16_t part2,
std:: uint16_t part3,
std:: uint16_t part4,
std:: uint16_t part5,
std:: uint16_t part6,
std:: uint16_t part7);
explicit constexpr ipv6_address (
const std::uint16_t (&parts)[8]);
explicit constexpr ipv6_address (
const std::uint8_t (bytes)[16]);
constexpr const bytes_t & bytes () const ;
constexpr std:: uint64_t subnet_prefix () const ;
constexpr std:: uint64_t interface_identifier () const ;
static constexpr ipv6_address unspecified ();
static constexpr ipv6_address loopback ();
static std::optional<ipv6_address> from_string (std::string_view string) noexcept ;
std::string to_string () const ;
constexpr bool operator ==( const ipv6_address& other) const ;
constexpr bool operator !=( const ipv6_address& other) const ;
constexpr bool operator <( const ipv6_address& other) const ;
constexpr bool operator >( const ipv6_address& other) const ;
constexpr bool operator <=( const ipv6_address& other) const ;
constexpr bool operator >=( const ipv6_address& other) const ;
};
class ip_address
{
public:
// Constructs to IPv4 address 0.0.0.0
ip_address () noexcept ;
ip_address (ipv4_address address) noexcept ;
ip_address (ipv6_address address) noexcept ;
bool is_ipv4 () const noexcept ;
bool is_ipv6 () const noexcept ;
const ipv4_address& to_ipv4 () const ;
const ipv6_address& to_ipv6 () const ;
const std:: uint8_t * bytes () const noexcept ;
std::string to_string () const ;
static std::optional<ip_address> from_string (std::string_view string) noexcept ;
bool operator ==( const ip_address& rhs) const noexcept ;
bool operator !=( const ip_address& rhs) const noexcept ;
// ipv4_address sorts less than ipv6_address
bool operator <( const ip_address& rhs) const noexcept ;
bool operator >( const ip_address& rhs) const noexcept ;
bool operator <=( const ip_address& rhs) const noexcept ;
bool operator >=( const ip_address& rhs) const noexcept ;
};
}ip_endpoint , ipv4_endpoint ipv6_endpointคลาสผู้ช่วยสำหรับแสดงที่อยู่ IP และหมายเลขพอร์ต
บทสรุป API:
namespace cppcoro ::net
{
class ipv4_endpoint
{
public:
ipv4_endpoint () noexcept ;
explicit ipv4_endpoint (ipv4_address address, std:: uint16_t port = 0 ) noexcept ;
const ipv4_address& address () const noexcept ;
std:: uint16_t port () const noexcept ;
std::string to_string () const ;
static std::optional<ipv4_endpoint> from_string (std::string_view string) noexcept ;
};
bool operator ==( const ipv4_endpoint& a, const ipv4_endpoint& b);
bool operator !=( const ipv4_endpoint& a, const ipv4_endpoint& b);
bool operator <( const ipv4_endpoint& a, const ipv4_endpoint& b);
bool operator >( const ipv4_endpoint& a, const ipv4_endpoint& b);
bool operator <=( const ipv4_endpoint& a, const ipv4_endpoint& b);
bool operator >=( const ipv4_endpoint& a, const ipv4_endpoint& b);
class ipv6_endpoint
{
public:
ipv6_endpoint () noexcept ;
explicit ipv6_endpoint (ipv6_address address, std:: uint16_t port = 0 ) noexcept ;
const ipv6_address& address () const noexcept ;
std:: uint16_t port () const noexcept ;
std::string to_string () const ;
static std::optional<ipv6_endpoint> from_string (std::string_view string) noexcept ;
};
bool operator ==( const ipv6_endpoint& a, const ipv6_endpoint& b);
bool operator !=( const ipv6_endpoint& a, const ipv6_endpoint& b);
bool operator <( const ipv6_endpoint& a, const ipv6_endpoint& b);
bool operator >( const ipv6_endpoint& a, const ipv6_endpoint& b);
bool operator <=( const ipv6_endpoint& a, const ipv6_endpoint& b);
bool operator >=( const ipv6_endpoint& a, const ipv6_endpoint& b);
class ip_endpoint
{
public:
// Constructs to IPv4 end-point 0.0.0.0:0
ip_endpoint () noexcept ;
ip_endpoint (ipv4_endpoint endpoint) noexcept ;
ip_endpoint (ipv6_endpoint endpoint) noexcept ;
bool is_ipv4 () const noexcept ;
bool is_ipv6 () const noexcept ;
const ipv4_endpoint& to_ipv4 () const ;
const ipv6_endpoint& to_ipv6 () const ;
ip_address address () const noexcept ;
std:: uint16_t port () const noexcept ;
std::string to_string () const ;
static std::optional<ip_endpoint> from_string (std::string_view string) noexcept ;
bool operator ==( const ip_endpoint& rhs) const noexcept ;
bool operator !=( const ip_endpoint& rhs) const noexcept ;
// ipv4_endpoint sorts less than ipv6_endpoint
bool operator <( const ip_endpoint& rhs) const noexcept ;
bool operator >( const ip_endpoint& rhs) const noexcept ;
bool operator <=( const ip_endpoint& rhs) const noexcept ;
bool operator >=( const ip_endpoint& rhs) const noexcept ;
};
}sync_wait() ฟังก์ชั่น sync_wait() สามารถใช้เพื่อรอแบบซิงโครนัสจนกว่าจะเสร็จสิ้น awaitable ที่ระบุ
การรอคอยที่ระบุจะเป็น co_await ed บนเธรดปัจจุบันภายใน coroutine ที่สร้างขึ้นใหม่
การโทร sync_wait() จะบล็อกจนกว่าการดำเนินการจะเสร็จสิ้นและจะส่งคืนผลลัพธ์ของนิพจน์ co_await หรือปรับเปลี่ยนข้อยกเว้นหากนิพจน์ co_await เสร็จสมบูรณ์ด้วยข้อยกเว้นที่ไม่มีการจัดการ
ฟังก์ชั่น sync_wait() ส่วนใหญ่มีประโยชน์สำหรับการเริ่มงานระดับบนสุดจากภายใน main() และรอจนกว่างานจะเสร็จสิ้นในทางปฏิบัติมันเป็นวิธีเดียวที่จะเริ่ม task แรก/ระดับสูงสุด
สรุป API:
// <cppcoro/sync_wait.hpp>
namespace cppcoro
{
template < typename AWAITABLE>
auto sync_wait (AWAITABLE&& awaitable)
-> typename awaitable_traits<AWAITABLE&&>::await_result_t;
}ตัวอย่าง:
void example_task ()
{
auto makeTask = []() -> task<std::string>
{
co_return " foo " ;
};
auto task = makeTask ();
// start the lazy task and wait until it completes
sync_wait (task); // -> "foo"
sync_wait ( makeTask ()); // -> "foo"
}
void example_shared_task ()
{
auto makeTask = []() -> shared_task<std::string>
{
co_return " foo " ;
};
auto task = makeTask ();
// start the shared task and wait until it completes
sync_wait (task) == " foo " ;
sync_wait ( makeTask ()) == " foo " ;
}when_all_ready() ฟังก์ชั่น when_all_ready() สามารถใช้เพื่อสร้างใหม่ที่รอคอยที่เสร็จสมบูรณ์เมื่ออินพุตทั้งหมดที่รอคอยเสร็จสมบูรณ์
งานอินพุตสามารถรอได้ทุกประเภท
เมื่อผู้ที่ถูกส่งคืนสามารถรอได้คือ co_await ed มันจะ co_await แต่ละอินพุตที่รอคอยในการเปิดเธรดที่กำลังรอตามลำดับพวกเขาจะถูกส่งผ่านไปยังฟังก์ชั่น when_all_ready() หากงานเหล่านี้ไม่สมบูรณ์แบบซิงโครนัสพวกเขาจะดำเนินการพร้อมกัน
เมื่อนิพจน์ co_await ทั้งหมดเกี่ยวกับอินพุตที่รอคอยได้ทำงานเพื่อให้เสร็จสิ้นการตอบกลับที่ส่งคืนจะเสร็จสมบูรณ์จะเสร็จสมบูรณ์และกลับมาใช้ Coroutine ที่กำลังรอ coroutine ที่รอคอยจะกลับมาทำงานต่อในเธรดของอินพุตที่รอคอยซึ่งจะเสร็จสมบูรณ์
การรอคอยที่สามารถกลับมาได้นั้นรับประกันว่าจะไม่โยนข้อยกเว้นเมื่อ co_await ed แม้ว่าอินพุตบางตัวที่รอคอยจะล้มเหลวด้วยข้อยกเว้นที่ไม่ได้รับการจัดการ
อย่างไรก็ตามโปรดทราบว่าการโทร when_all_ready() ตัวเองอาจโยน std::bad_alloc หากไม่สามารถจัดสรรหน่วยความจำสำหรับเฟรม coroutine ที่จำเป็นในการรอการป้อนข้อมูลแต่ละครั้งที่รออยู่ นอกจากนี้ยังอาจมีข้อยกเว้นหากอินพุตใด ๆ ที่รอวัตถุที่รอคอยจากตัวสร้างสำเนา/ย้ายของพวกเขา
ผลลัพธ์ของ co_await ing returned ที่รอคอยคือ std::tuple หรือ std::vector ของ when_all_task<RESULT> วัตถุ วัตถุเหล่านี้ช่วยให้คุณได้รับผลลัพธ์ (หรือข้อยกเว้น) ของแต่ละอินพุตที่รอคอยแยกกันโดยเรียกใช้วิธี when_all_task<RESULT>::result() ของงานเอาต์พุตที่สอดคล้องกัน สิ่งนี้ช่วยให้ผู้โทรสามารถรอคอยหลาย ๆ คนรอคอยและซิงโครไนซ์เมื่อเสร็จสมบูรณ์ในขณะที่ยังคงรักษาความสามารถในการตรวจสอบผลลัพธ์ของการดำเนินการ co_await แต่ละครั้งเพื่อความสำเร็จ/ความล้มเหลว
สิ่งนี้แตกต่างจาก when_all() ที่ความล้มเหลวของการดำเนินการ co_await แต่ละตัวทำให้การดำเนินการโดยรวมล้มเหลวโดยมีข้อยกเว้น ซึ่งหมายความว่าคุณไม่สามารถระบุได้ว่าการดำเนินการ co_await ของส่วนประกอบใดที่ล้มเหลวและป้องกันไม่ให้คุณได้รับผลลัพธ์ของการดำเนินการ co_await อื่น ๆ
สรุป API:
// <cppcoro/when_all_ready.hpp>
namespace cppcoro
{
// Concurrently await multiple awaitables.
//
// Returns an awaitable object that, when co_await'ed, will co_await each of the input
// awaitable objects and will resume the awaiting coroutine only when all of the
// component co_await operations complete.
//
// Result of co_await'ing the returned awaitable is a std::tuple of detail::when_all_task<T>,
// one for each input awaitable and where T is the result-type of the co_await expression
// on the corresponding awaitable.
//
// AWAITABLES must be awaitable types and must be movable (if passed as rvalue) or copyable
// (if passed as lvalue). The co_await expression will be executed on an rvalue of the
// copied awaitable.
template < typename ... AWAITABLES>
auto when_all_ready (AWAITABLES&&... awaitables)
-> Awaitable<std::tuple<detail::when_all_task<typename awaitable_traits<AWAITABLES>::await_result_t>...>>;
// Concurrently await each awaitable in a vector of input awaitables.
template <
typename AWAITABLE,
typename RESULT = typename awaitable_traits<AWAITABLE>:: await_result_t >
auto when_all_ready (std::vector<AWAITABLE> awaitables)
-> Awaitable<std::vector<detail::when_all_task<RESULT>>>;
}ตัวอย่างการใช้งาน:
task<std::string> get_record ( int id);
task<> example1 ()
{
// Run 3 get_record() operations concurrently and wait until they're all ready.
// Returns a std::tuple of tasks that can be unpacked using structured bindings.
auto [task1, task2, task3] = co_await when_all_ready (
get_record ( 123 ),
get_record ( 456 ),
get_record ( 789 ));
// Unpack the result of each task
std::string& record1 = task1. result ();
std::string& record2 = task2. result ();
std::string& record3 = task3. result ();
// Use records....
}
task<> example2 ()
{
// Create the input tasks. They don't start executing yet.
std::vector<task<std::string>> tasks;
for ( int i = 0 ; i < 1000 ; ++i)
{
tasks. emplace_back ( get_record (i));
}
// Execute all tasks concurrently.
std::vector<detail::when_all_task<std::string>> resultTasks =
co_await when_all_ready ( std::move (tasks));
// Unpack and handle each result individually once they're all complete.
for ( int i = 0 ; i < 1000 ; ++i)
{
try
{
std::string& record = tasks[i]. result ();
std::cout << i << " = " << record << std::endl;
}
catch ( const std:: exception & ex)
{
std::cout << i << " : " << ex. what () << std::endl;
}
}
}when_all() ฟังก์ชั่น when_all() สามารถใช้เพื่อสร้างใหม่ที่รอคอยว่าเมื่อ co_await ed จะ co_await แต่ละอินพุตที่รอคอยพร้อมกันและส่งคืนรวมของผลลัพธ์แต่ละรายการ
เมื่อรอคอยที่จะคืนได้จะรอคอยมันจะ co_await แต่ละอินพุตที่รออยู่ในเธรดปัจจุบัน เมื่อการระงับครั้งแรกที่รอคอยงานที่สองจะเริ่มต้นขึ้นและอื่น ๆ การดำเนินการดำเนินการพร้อมกันจนกว่าพวกเขาจะทำงานให้เสร็จสมบูรณ์
เมื่อการดำเนินการส่วนประกอบทั้งหมด co_await ทำงานให้เสร็จสมบูรณ์การรวมของผลลัพธ์จะถูกสร้างขึ้นจากผลลัพธ์แต่ละรายการ หากข้อยกเว้นถูกโยนทิ้งโดยงานอินพุตใด ๆ หรือหากการก่อสร้างผลรวมนั้นมีข้อยกเว้นข้อยกเว้นจะเผยแพร่ออกมาจาก co_await ของผู้ที่รอคอย
หากการดำเนินการ co_await หลายครั้งล้มเหลวโดยมีข้อยกเว้นหนึ่งในข้อยกเว้นจะเผยแพร่ออกจากนิพจน์ co_await when_all() นิพจน์ข้อยกเว้นอื่น ๆ จะถูกละเว้นอย่างเงียบ ๆ ไม่ได้ระบุว่าจะเลือกข้อยกเว้นของการดำเนินการใด
หากเป็นสิ่งสำคัญที่จะต้องรู้ว่าการดำเนินการ co_await ส่วนประกอบใดล้มเหลวหรือรักษาความสามารถในการรับผลลัพธ์ของการดำเนินการอื่น ๆ แม้ว่าบางส่วนจะล้มเหลวคุณควรใช้ when_all_ready() แทน
สรุป API:
// <cppcoro/when_all.hpp>
namespace cppcoro
{
// Variadic version.
//
// Note that if the result of `co_await awaitable` yields a void-type
// for some awaitables then the corresponding component for that awaitable
// in the tuple will be an empty struct of type detail::void_value.
template < typename ... AWAITABLES>
auto when_all (AWAITABLES&&... awaitables)
-> Awaitable<std::tuple<typename awaitable_traits<AWAITABLES>::await_result_t...>>;
// Overload for vector<Awaitable<void>>.
template <
typename AWAITABLE,
typename RESULT = typename awaitable_traits<AWAITABLE>:: await_result_t ,
std:: enable_if_t <std::is_void_v<RESULT>, int > = 0 >
auto when_all (std::vector<AWAITABLE> awaitables)
-> Awaitable<void>;
// Overload for vector<Awaitable<NonVoid>> that yield a value when awaited.
template <
typename AWAITABLE,
typename RESULT = typename awaitable_traits<AWAITABLE>:: await_result_t ,
std:: enable_if_t <!std::is_void_v<RESULT>, int > = 0 >
auto when_all (std::vector<AWAITABLE> awaitables)
-> Awaitable<std::vector<std::conditional_t<
std::is_lvalue_reference_v<RESULT>,
std::reference_wrapper<std::remove_reference_t<RESULT>>,
std::remove_reference_t<RESULT>>>>;
}ตัวอย่าง:
task<A> get_a ();
task<B> get_b ();
task<> example1 ()
{
// Run get_a() and get_b() concurrently.
// Task yields a std::tuple<A, B> which can be unpacked using structured bindings.
auto [a, b] = co_await when_all ( get_a (), get_b ());
// use a, b
}
task<std::string> get_record ( int id);
task<> example2 ()
{
std::vector<task<std::string>> tasks;
for ( int i = 0 ; i < 1000 ; ++i)
{
tasks. emplace_back ( get_record (i));
}
// Concurrently execute all get_record() tasks.
// If any of them fail with an exception then the exception will propagate
// out of the co_await expression once they have all completed.
std::vector<std::string> records = co_await when_all ( std::move (tasks));
// Process results
for ( int i = 0 ; i < 1000 ; ++i)
{
std::cout << i << " = " << records[i] << std::endl;
}
}fmap() ฟังก์ชั่น fmap() สามารถใช้ในการใช้ฟังก์ชัน callable กับค่าที่อยู่ในประเภทคอนเทนเนอร์ส่งคืนประเภทคอนเทนเนอร์ใหม่ของผลลัพธ์ของการใช้ฟังก์ชันค่าที่มีอยู่
ฟังก์ชั่น fmap() สามารถใช้ฟังก์ชันกับค่าของ generator<T> , recursive_generator<T> และ async_generator<T> รวมถึงค่าใด ๆ ที่รองรับแนวคิด Awaitable (เช่น task<T> )
แต่ละประเภทเหล่านี้มีการโอเวอร์โหลดสำหรับ fmap() ที่ใช้สองอาร์กิวเมนต์; ฟังก์ชั่นที่จะใช้และค่าคอนเทนเนอร์ ดูเอกสารสำหรับแต่ละประเภทสำหรับ fmap() โอเวอร์โหลด
ตัวอย่างเช่นฟังก์ชั่น fmap() สามารถใช้ในการใช้ฟังก์ชั่นกับผลลัพธ์ในที่สุดของ task<T> สร้าง task<U> ที่จะเสร็จสมบูรณ์ด้วยค่าส่งคืนของฟังก์ชั่น
// Given a function you want to apply that converts
// a value of type A to value of type B.
B a_to_b (A value);
// And a task that yields a value of type A
cppcoro::task<A> get_an_a ();
// We can apply the function to the result of the task using fmap()
// and obtain a new task yielding the result.
cppcoro::task<B> bTask = fmap(a_to_b, get_an_a());
// An alternative syntax is to use the pipe notation.
cppcoro::task<B> bTask = get_an_a() | cppcoro::fmap(a_to_b);สรุป API:
// <cppcoro/fmap.hpp>
namespace cppcoro
{
template < typename FUNC>
struct fmap_transform
{
fmap_transform (FUNC&& func) noexcept (std::is_nothrow_move_constructible_v<FUNC>);
FUNC func;
};
// Type-deducing constructor for fmap_transform object that can be used
// in conjunction with operator|.
template < typename FUNC>
fmap_transform<FUNC> fmap (FUNC&& func);
// operator| overloads for providing pipe-based syntactic sugar for fmap()
// such that the expression:
// <value-expr> | cppcoro::fmap(<func-expr>)
// is equivalent to:
// fmap(<func-expr>, <value-expr>)
template < typename T, typename FUNC>
decltype ( auto ) operator |(T&& value, fmap_transform<FUNC>&& transform);
template < typename T, typename FUNC>
decltype ( auto ) operator |(T&& value, fmap_transform<FUNC>& transform);
template < typename T, typename FUNC>
decltype ( auto ) operator |(T&& value, const fmap_transform<FUNC>& transform);
// Generic overload for all awaitable types.
//
// Returns an awaitable that when co_awaited, co_awaits the specified awaitable
// and applies the specified func to the result of the 'co_await awaitable'
// expression as if by 'std::invoke(func, co_await awaitable)'.
//
// If the type of 'co_await awaitable' expression is 'void' then co_awaiting the
// returned awaitable is equivalent to 'co_await awaitable, func()'.
template <
typename FUNC,
typename AWAITABLE,
std:: enable_if_t <is_awaitable_v<AWAITABLE>, int > = 0 >
auto fmap (FUNC&& func, AWAITABLE&& awaitable)
-> Awaitable<std::invoke_result_t<FUNC, typename awaitable_traits<AWAITABLE>::await_result_t>>;
} ฟังก์ชั่น fmap() ได้รับการออกแบบมาเพื่อค้นหาการโอเวอร์โหลดที่ถูกต้องโดยการค้นหาที่ขึ้นอยู่กับอาร์กิวเมนต์ (ADL) ดังนั้นโดยทั่วไปควรเรียกว่าโดยไม่ต้องใช้คำนำหน้า cppcoro::
resume_on() ฟังก์ชั่น resume_on() สามารถใช้ในการควบคุมบริบทการดำเนินการที่ผู้ที่รอคอยจะกลับมาใช้งาน coroutine ที่รออยู่เมื่อรออยู่ เมื่อนำไปใช้กับ async_generator มันจะควบคุมบริบทการดำเนินการที่ co_await g.begin() และ co_await ++it การดำเนินการด้านไอทีกลับมาทำงานต่อไป
โดยปกติแล้วการรอคอย coroutine ของงานที่รอคอย (เช่น task ) หรือ async_generator จะดำเนินการต่อในการดำเนินการใด ๆ ที่การดำเนินการเสร็จสมบูรณ์ ในบางกรณีนี่อาจไม่ใช่เธรดที่คุณต้องการดำเนินการต่อไป ในกรณีเหล่านี้คุณสามารถใช้ฟังก์ชั่น resume_on() เพื่อสร้างใหม่ที่รอคอยหรือเครื่องกำเนิดไฟฟ้าที่จะดำเนินการดำเนินการต่อในเธรดที่เกี่ยวข้องกับตัวกำหนดตารางเวลาที่ระบุ
ฟังก์ชั่น resume_on() สามารถใช้เป็นฟังก์ชั่นปกติที่ส่งคืนใหม่ที่รอคอย/เครื่องกำเนิดไฟฟ้า หรือสามารถใช้ใน pipeline-syntax
ตัวอย่าง:
task<record> load_record ( int id);
ui_thread_scheduler uiThreadScheduler;
task<> example ()
{
// This will start load_record() on the current thread.
// Then when load_record() completes (probably on an I/O thread)
// it will reschedule execution onto thread pool and call to_json
// Once to_json completes it will transfer execution onto the
// ui thread before resuming this coroutine and returning the json text.
task<std::string> jsonTask =
load_record ( 123 )
| cppcoro::resume_on ( threadpool::default ())
| cppcoro::fmap (to_json)
| cppcoro::resume_on (uiThreadScheduler);
// At this point, all we've done is create a pipeline of tasks.
// The tasks haven't started executing yet.
// Await the result. Starts the pipeline of tasks.
std::string jsonText = co_await jsonTask;
// Guaranteed to be executing on ui thread here.
someUiControl. set_text (jsonText);
}สรุป API:
// <cppcoro/resume_on.hpp>
namespace cppcoro
{
template < typename SCHEDULER, typename AWAITABLE>
auto resume_on (SCHEDULER& scheduler, AWAITABLE awaitable)
-> Awaitable<typename awaitable_traits<AWAITABLE>::await_traits_t>;
template < typename SCHEDULER, typename T>
async_generator<T> resume_on (SCHEDULER& scheduler, async_generator<T> source);
template < typename SCHEDULER>
struct resume_on_transform
{
explicit resume_on_transform (SCHEDULER& scheduler) noexcept ;
SCHEDULER& scheduler;
};
// Construct a transform/operation that can be applied to a source object
// using "pipe" notation (ie. operator|).
template < typename SCHEDULER>
resume_on_transform<SCHEDULER> resume_on (SCHEDULER& scheduler) noexcept ;
// Equivalent to 'resume_on(transform.scheduler, std::forward<T>(value))'
template < typename T, typename SCHEDULER>
decltype ( auto ) operator |(T&& value, resume_on_transform<SCHEDULER> transform)
{
return resume_on (transform. scheduler , std::forward<T>(value));
}
}schedule_on() ฟังก์ชั่น schedule_on() สามารถใช้เพื่อเปลี่ยนบริบทการดำเนินการที่กำหนดให้รอหรือ async_generator เริ่มดำเนินการ
เมื่อนำไปใช้กับ async_generator มันจะส่งผลกระทบต่อบริบทการดำเนินการใดที่จะดำเนินการต่อหลังจากคำสั่ง co_yield
โปรดทราบว่าการแปลง schedule_on ไม่ได้ระบุเธรดที่รอคอยหรือ async_generator จะเสร็จสมบูรณ์หรือให้ผลลัพธ์ที่ขึ้นอยู่กับการใช้งานของผู้ที่รอคอยหรือเครื่องกำเนิด
ดูตัวดำเนิน resume_on() สำหรับการแปลงที่ควบคุมเธรดการดำเนินการเสร็จสมบูรณ์
ตัวอย่างเช่น:
task< int > get_value ();
io_service ioSvc;
task<> example ()
{
// Starts executing get_value() on the current thread.
int a = co_await get_value ();
// Starts executing get_value() on a thread associated with ioSvc.
int b = co_await schedule_on (ioSvc, get_value ());
}สรุป API:
// <cppcoro/schedule_on.hpp>
namespace cppcoro
{
// Return a task that yields the same result as 't' but that
// ensures that 't' is co_await'ed on a thread associated with
// the specified scheduler. Resulting task will complete on
// whatever thread 't' would normally complete on.
template < typename SCHEDULER, typename AWAITABLE>
auto schedule_on (SCHEDULER& scheduler, AWAITABLE awaitable)
-> Awaitable<typename awaitable_traits<AWAITABLE>::await_result_t>;
// Return a generator that yields the same sequence of results as
// 'source' but that ensures that execution of the coroutine starts
// execution on a thread associated with 'scheduler' and resumes
// after a 'co_yield' on a thread associated with 'scheduler'.
template < typename SCHEDULER, typename T>
async_generator<T> schedule_on (SCHEDULER& scheduler, async_generator<T> source);
template < typename SCHEDULER>
struct schedule_on_transform
{
explicit schedule_on_transform (SCHEDULER& scheduler) noexcept ;
SCHEDULER& scheduler;
};
template < typename SCHEDULER>
schedule_on_transform<SCHEDULER> schedule_on (SCHEDULER& scheduler) noexcept ;
template < typename T, typename SCHEDULER>
decltype ( auto ) operator |(T&& value, schedule_on_transform<SCHEDULER> transform);
}awaitable_traits<T> เทมเพลต metafunction นี้สามารถใช้เพื่อกำหนดประเภทที่ผลลัพธ์ของนิพจน์ co_await จะเป็นอย่างไรหากนำไปใช้กับนิพจน์ประเภท T
โปรดทราบว่าสิ่งนี้จะถือว่าค่าของ Type T กำลังรออยู่ในบริบทที่ไม่ได้รับผลกระทบจาก await_transform ใด ๆ ที่ใช้โดยวัตถุสัญญาของ Coroutine ผลลัพธ์อาจแตกต่างกันหากค่าประเภท T รออยู่ในบริบทดังกล่าว
เทมเพลต metafunction awaitable_traits<T> ไม่สามารถกำหนด awaiter_t หรือ await_result_t typedefs ซ้อนกันได้ถ้าพิมพ์, T , ไม่สามารถรอได้ สิ่งนี้ช่วยให้การใช้งานในบริบท Sfinae ที่ปิดใช้งานการโอเวอร์โหลดเมื่อ T ไม่สามารถรอได้
สรุป API:
// <cppcoro/awaitable_traits.hpp>
namespace cppcoro
{
template < typename T>
struct awaitable_traits
{
// The type that results from applying `operator co_await()` to a value
// of type T, if T supports an `operator co_await()`, otherwise is type `T&&`.
typename awaiter_t = <unspecified>;
// The type of the result of co_await'ing a value of type T.
typename await_result_t = <unspecified>;
};
}is_awaitable<T> metafunction is_awaitable<T> metafunction ช่วยให้คุณสามารถสอบถามได้ว่าประเภทที่กำหนดสามารถเป็น co_await ed หรือไม่จากภายใน coroutine หรือไม่
สรุป API:
// <cppcoro/is_awaitable.hpp>
namespace cppcoro
{
template < typename T>
struct is_awaitable : std::bool_constant<...>
{};
template < typename T>
constexpr bool is_awaitable_v = is_awaitable<T>::value;
}Awaitable<T> Awaitable<T> คือแนวคิดที่บ่งชี้ว่าประเภทสามารถเป็น co_await ed ในบริบท coroutine ที่ไม่มีการ overloads await_transform และผลลัพธ์ของการแสดงออก co_await มีประเภท T
ตัวอย่างเช่น Type task<T> ใช้แนวคิด Awaitable<T&&> ในขณะที่ Type task<T>& ใช้แนวคิด Awaitable<T&>
Awaiter<T> แนวคิด Awaiter<T> เป็นแนวคิดที่บ่งบอกถึงประเภทที่มีวิธีการ await_ready , await_suspend และ await_resume ที่จำเป็นในการใช้โปรโตคอลสำหรับการระงับ/กลับมาใช้ coroutine ที่กำลังรออยู่
ประเภทที่ตอบสนอง Awaiter<T> ต้องมีตัวอย่างของประเภท awaiter :
awaiter.await_ready() -> boolawaiter.await_suspend(std::experimental::coroutine_handle<void>{}) -> void หรือ bool หรือ std::experimental::coroutine_handle<P> สำหรับ Pawaiter.await_resume() -> T ประเภทใด ๆ ที่ใช้แนวคิด Awaiter<T> ยังใช้แนวคิด Awaitable<T>
Scheduler Scheduler เป็นแนวคิดที่อนุญาตให้มีการกำหนดเวลาการดำเนินการของ coroutines ภายในบริบทการดำเนินการบางอย่าง
concept Scheduler
{
Awaitable< void > schedule ();
} ให้ประเภท, S , ที่ใช้แนวคิด Scheduler และอินสแตนซ์, s , ประเภท S :
s.schedule() ส่งคืนประเภทที่รอคอยเช่น co_await s.schedule() จะระงับ coroutine ปัจจุบันอย่างไม่มีเงื่อนไขและกำหนดเวลาสำหรับการเริ่มต้นใหม่ในบริบทการดำเนินการที่เกี่ยวข้องกับตัวกำหนดตาราง sco_await s.schedule() มี void ประเภท cppcoro::task<> f (Scheduler& scheduler)
{
// Execution of the coroutine is initially on the caller's execution context.
// Suspends execution of the coroutine and schedules it for resumption on
// the scheduler's execution context.
co_await scheduler. schedule ();
// At this point the coroutine is now executing on the scheduler's
// execution context.
}DelayedScheduler DelayedScheduler เป็นแนวคิดที่อนุญาตให้ coroutine กำหนดเวลาสำหรับการดำเนินการตามบริบทการดำเนินการของกำหนดเวลาหลังจากระยะเวลาที่กำหนดได้ผ่านไปแล้ว
concept DelayedScheduler : Scheduler
{
template < typename REP, typename RATIO>
Awaitable< void > schedule_after (std::chrono::duration<REP, RATIO> delay);
template < typename REP, typename RATIO>
Awaitable< void > schedule_after (
std::chrono::duration<REP, RATIO> delay,
cppcoro::cancellation_token cancellationToken);
} ให้ประเภท, S , ที่ใช้ DelayedScheduler และอินสแตนซ์, s ของ type S :
s.schedule_after(delay) ส่งคืนวัตถุที่สามารถรอได้ว่า co_await s.schedule_after(delay) ระงับ coroutine ปัจจุบันในช่วงเวลาของ delay ก่อนที่จะกำหนดเวลา coroutine สำหรับการเริ่มต้นบริบทการดำเนินการที่เกี่ยวข้องกับตัวกำหนดตาราง sco_await s.schedule_after(delay) มี void ประเภทห้องสมุด CPPCORO รองรับการสร้างภายใต้ Windows ด้วย Visual Studio 2017 และ Linux ด้วย Clang 5.0+
ห้องสมุดนี้ใช้ประโยชน์จากระบบสร้างเค้ก (ไม่ไม่ใช่ C# หนึ่ง)
ระบบสร้างเค้กจะถูกตรวจสอบโดยอัตโนมัติเป็น submodule git ดังนั้นคุณไม่จำเป็นต้องดาวน์โหลดหรือติดตั้งแยกต่างหาก
ปัจจุบันห้องสมุดนี้ต้องการ Visual Studio 2017 หรือใหม่กว่าและ Windows 10 SDK
มีการวางแผนการสนับสนุน Clang (#3) และ Linux (#15)
ระบบสร้างเค้กถูกนำไปใช้ใน Python และต้องติดตั้ง Python 2.7
ตรวจสอบให้แน่ใจว่า Python 2.7 Interpreter อยู่ในเส้นทางของคุณและมีให้เป็น 'Python'
ตรวจสอบให้แน่ใจว่ามีการติดตั้ง Visual Studio 2017 3 หรือใหม่กว่า โปรดทราบว่ามีปัญหาบางอย่างที่ทราบกับ coroutines ใน Update 2 หรือก่อนหน้านี้ที่ได้รับการแก้ไขในการอัปเดต 3
นอกจากนี้คุณยังสามารถใช้คอมไพเลอร์ Visual Studio เวอร์ชันทดลองได้โดยการดาวน์โหลดแพ็คเกจ NUGET จาก https://vcppdogfooding.azurewebsites.net/ และคลายไฟล์. nuget ไปยังไดเรกทอรี เพียงอัปเดตไฟล์ config.cake เพื่อชี้ไปที่ตำแหน่งที่คลายซิปโดยการแก้ไขและไม่ทำให้ไม่รู้สึกถึงบรรทัดต่อไปนี้:
nugetPath = None # r'C:PathToVisualCppTools.14.0.25224-Pre'ตรวจสอบให้แน่ใจว่าคุณติดตั้ง Windows 10 SDK มันจะใช้เวอร์ชัน Windows 10 SDK ล่าสุดและ Universal C Runtime ตามค่าเริ่มต้น
ที่เก็บ CPPCORO ใช้ประโยชน์จาก git submodules เพื่อดึงในแหล่งสำหรับระบบสร้างเค้ก
ซึ่งหมายความว่าคุณต้องส่งธง --recursive กลับไปยังคำสั่ง git clone เช่น
c:Code> git clone --recursive https://github.com/lewissbaker/cppcoro.git
หากคุณได้โคลน CPPCORO อยู่แล้วคุณควรอัปเดต submodules หลังจากดึงการเปลี่ยนแปลง
c:Codecppcoro> git submodule update --init --recursive
ในการสร้างจากบรรทัดคำสั่งเพียงเรียกใช้ 'cake.bat' ในรูทเวิร์กสเปซ
เช่น
C:cppcoro> cake.bat
Building with C:cppcoroconfig.cake - Variant(release='debug', platform='windows', architecture='x86', compilerFamily='msvc', compiler='msvc14.10')
Building with C:cppcoroconfig.cake - Variant(release='optimised', platform='windows', architecture='x64', compilerFamily='msvc', compiler='msvc14.10')
Building with C:cppcoroconfig.cake - Variant(release='debug', platform='windows', architecture='x64', compilerFamily='msvc', compiler='msvc14.10')
Building with C:cppcoroconfig.cake - Variant(release='optimised', platform='windows', architecture='x86', compilerFamily='msvc', compiler='msvc14.10')
Compiling testmain.cpp
Compiling testmain.cpp
Compiling testmain.cpp
Compiling testmain.cpp
...
Linking buildwindows_x86_msvc14.10_debugtestrun.exe
Linking buildwindows_x64_msvc14.10_optimisedtestrun.exe
Linking buildwindows_x86_msvc14.10_optimisedtestrun.exe
Linking buildwindows_x64_msvc14.10_debugtestrun.exe
Generating code
Finished generating code
Generating code
Finished generating code
Build succeeded.
Build took 0:00:02.419.
โดยค่าเริ่มต้นการเรียกใช้ cake ที่ไม่มีอาร์กิวเมนต์จะสร้างโครงการทั้งหมดด้วยตัวแปรสร้างทั้งหมดและดำเนินการทดสอบหน่วย คุณสามารถ จำกัด สิ่งที่สร้างขึ้นโดยผ่านอาร์กิวเมนต์บรรทัดคำสั่งเพิ่มเติม เช่น
c:cppcoro> cake.bat release=debug architecture=x64 lib/build.cake
Building with C:UsersLewisCodecppcoroconfig.cake - Variant(release='debug', platform='windows', architecture='x64', compilerFamily='msvc', compiler='msvc14.10')
Archiving buildwindows_x64_msvc14.10_debuglibcppcoro.lib
Build succeeded.
Build took 0:00:00.321.
คุณสามารถเรียกใช้ cake --help ในรายการตัวเลือกบรรทัดคำสั่งที่มีอยู่
ในการพัฒนาจากภายใน Visual Studio คุณสามารถสร้างไฟล์. VCPROJ/.SLN โดยใช้ cake.bat -p
เช่น
c:cppcoro> cake.bat -p
Building with C:cppcoroconfig.cake - Variant(release='debug', platform='windows', architecture='x86', compilerFamily='msvc', compiler='msvc14.10')
Building with C:cppcoroconfig.cake - Variant(release='optimised', platform='windows', architecture='x64', compilerFamily='msvc', compiler='msvc14.10')
Building with C:cppcoroconfig.cake - Variant(release='debug', platform='windows', architecture='x64', compilerFamily='msvc', compiler='msvc14.10')
Building with C:cppcoroconfig.cake - Variant(release='optimised', platform='windows', architecture='x86', compilerFamily='msvc', compiler='msvc14.10')
Generating Solution build/project/cppcoro.sln
Generating Project build/project/cppcoro_tests.vcxproj
Generating Filters build/project/cppcoro_tests.vcxproj.filters
Generating Project build/project/cppcoro.vcxproj
Generating Filters build/project/cppcoro.vcxproj.filters
Build succeeded.
Build took 0:00:00.247.
เมื่อคุณสร้างโครงการเหล่านี้จาก Inside Visual Studio มันจะเรียกเค้กเพื่อทำการรวบรวม
โครงการ CPPCORO ยังสามารถสร้างได้ภายใต้ Linux โดยใช้ Clang+ LIBC ++ 5.0 หรือใหม่กว่า
อาคาร CPPCORO ได้รับการทดสอบภายใต้ Ubuntu 17.04
ตรวจสอบให้แน่ใจว่าคุณติดตั้งแพ็คเกจต่อไปนี้:
นี่คือสมมติว่าคุณมี Clang และ LIBC ++ สร้างและติดตั้ง
หากคุณยังไม่ได้กำหนดค่าการส่งสัญญาณให้ดูที่ส่วนต่อไปนี้สำหรับรายละเอียดเกี่ยวกับการตั้งค่า Clang สำหรับการสร้างด้วย CPPCORO
Checkout CPPCORO และ Submodules:
git clone --recursive https://github.com/lewissbaker/cppcoro.git cppcoro
เรียกใช้ init.sh เพื่อตั้งค่าฟังก์ชั่น cake ทุบตี:
cd cppcoro
source init.sh
จากนั้นคุณสามารถเรียกใช้ cake จากรูทเวิร์กสเปซเพื่อสร้าง CPPCORO และเรียกใช้การทดสอบ:
$ cake
คุณสามารถระบุอาร์กิวเมนต์บรรทัดคำสั่งเพิ่มเติมเพื่อปรับแต่งบิลด์:
--help จะพิมพ์ความช่วยเหลือสำหรับอาร์กิวเมนต์บรรทัดคำสั่ง--debug=run จะแสดงการเรียกใช้คำสั่ง buildrelease=debug หรือ release=optimised จะ จำกัด ตัวแปร build ให้ดีบักหรือปรับให้เหมาะสม (โดยค่าเริ่มต้นจะสร้างทั้งสองอย่าง)lib/build.cake จะสร้างห้องสมุด CPPCORO และไม่ใช่การทดสอบtest/build.cake@task_tests.cpp จะรวบรวมไฟล์ต้นฉบับเฉพาะtest/build.cake@testresult จะสร้างและเรียกใช้การทดสอบตัวอย่างเช่น:
$ cake --debug=run release=debug lib/build.cake
หากคอมไพเลอร์ Clang ของคุณไม่ได้อยู่ที่ /usr/bin/clang คุณสามารถระบุตำแหน่งทางเลือกโดยใช้ตัวเลือกบรรทัดคำสั่งหนึ่งต่อไปนี้สำหรับ cake :
--clang-executable=<name> -ระบุชื่อ clang clang ที่ใช้งานได้แทนที่จะเป็น clang เช่น เพื่อบังคับให้ใช้ Clang 8.0 Pass --clang-executable=clang-8--clang-executable=<abspath> -ระบุเส้นทางเต็มไปยัง clang clang clang ระบบบิลด์จะมองหาหน้าที่อื่น ๆ ในไดเรกทอรีเดียวกัน หากเส้นทางนี้มีแบบฟอร์ม <prefix>/bin/<name> จากนั้นจะตั้งค่าการติดตั้ง clang-prefix เริ่มต้นเป็น <prefix>--clang-install-prefix=<path> -ระบุเส้นทางที่ติดตั้ง Clang แล้ว สิ่งนี้จะทำให้ระบบบิลด์มองหาเสียงดังกราวใต้ <path>/bin (เว้นแต่จะถูกแทนที่ด้วย --clang-executable )--libcxx-install-prefix=<path> -ระบุเส้นทางที่ติดตั้ง LIBC ++ แล้ว โดยค่าเริ่มต้นระบบบิลด์จะค้นหา LIBC ++ ในตำแหน่งเดียวกันกับเสียงดัง ใช้ตัวเลือกบรรทัดคำสั่งนี้หากติดตั้งในตำแหน่งอื่นตัวอย่าง: ใช้ Clang เวอร์ชันเฉพาะที่ติดตั้งในตำแหน่งเริ่มต้น
$ cake --clang-executable=clang-8
ตัวอย่าง: ใช้ Clang เวอร์ชันเริ่มต้นจากตำแหน่งที่กำหนดเอง
$ cake --clang-install-prefix=/path/to/clang-install
Example: Use a specific version of clang, in a custom location, with libc++ from a different location
$ cake --clang-executable=/path/to/clang-install/bin/clang-8 --libcxx-install-prefix=/path/to/libcxx-install
If your Linux distribution does not have a version of Clang 5.0 or later available, you can install a snapshot build from the LLVM project.
Follow instructions at http://apt.llvm.org/ to setup your package manager to support pulling from the LLVM package manager.
For example, for Ubuntu 17.04 Zesty:
Edit /etc/apt/sources.list and add the following lines:
deb http://apt.llvm.org/zesty/ llvm-toolchain-zesty main
deb-src http://apt.llvm.org/zesty/ llvm-toolchain-zesty main
Install the PGP key for those packages:
$ wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
Install Clang and LLD:
$ sudo apt-get install clang-6.0 lld-6.0
The LLVM snapshot builds do not include libc++ versions so you'll need to build that yourself. ดูด้านล่าง
You can also use the bleeding-edge Clang version by building Clang from source yourself.
See instructions here:
To do this you will need to install the following pre-requisites:
$ sudo apt-get install git cmake ninja-build clang lld
Note that we are using your distribution's version of clang to build clang from source. GCC could also be used here instead.
Checkout LLVM + Clang + LLD + libc++ repositories:
mkdir llvm
cd llvm
git clone --depth=1 https://github.com/llvm-mirror/llvm.git llvm
git clone --depth=1 https://github.com/llvm-mirror/clang.git llvm/tools/clang
git clone --depth=1 https://github.com/llvm-mirror/lld.git llvm/tools/lld
git clone --depth=1 https://github.com/llvm-mirror/libcxx.git llvm/projects/libcxx
ln -s llvm/tools/clang clang
ln -s llvm/tools/lld lld
ln -s llvm/projects/libcxx libcxx
Configure and build Clang:
mkdir clang-build
cd clang-build
cmake -GNinja
-DCMAKE_CXX_COMPILER=/usr/bin/clang++
-DCMAKE_C_COMPILER=/usr/bin/clang
-DCMAKE_BUILD_TYPE=MinSizeRel
-DCMAKE_INSTALL_PREFIX="/path/to/clang/install"
-DCMAKE_BUILD_WITH_INSTALL_RPATH="yes"
-DLLVM_TARGETS_TO_BUILD=X86
-DLLVM_ENABLE_PROJECTS="lld;clang"
../llvm
ninja install-clang
install-clang-headers
install-llvm-ar
install-lld
The cppcoro project requires libc++ as it contains the <experimental/coroutine> header required to use C++ coroutines under Clang.
Checkout libc++ + llvm :
mkdir llvm
cd llvm
git clone --depth=1 https://github.com/llvm-mirror/llvm.git llvm
git clone --depth=1 https://github.com/llvm-mirror/libcxx.git llvm/projects/libcxx
ln -s llvm/projects/libcxx libcxx
Build libc++ :
mkdir libcxx-build
cd libcxx-build
cmake -GNinja
-DCMAKE_CXX_COMPILER="/path/to/clang/install/bin/clang++"
-DCMAKE_C_COMPILER="/path/to/clang/install/bin/clang"
-DCMAKE_BUILD_TYPE=Release
-DCMAKE_INSTALL_PREFIX="/path/to/clang/install"
-DLLVM_PATH="../llvm"
-DLIBCXX_CXX_ABI=libstdc++
-DLIBCXX_CXX_ABI_INCLUDE_PATHS="/usr/include/c++/6.3.0/;/usr/include/x86_64-linux-gnu/c++/6.3.0/"
../libcxx
ninja cxx
ninja install
This will build and install libc++ into the same install directory where you have clang installed.
The cppcoro port in vcpkg is kept up to date by Microsoft team members and community contributors. The url of vcpkg is: https://github.com/Microsoft/vcpkg . You can download and install cppcoro using the vcpkg dependency manager:
git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
./bootstrap-vcpkg.sh # ./bootstrap-vcpkg.bat for Windows
./vcpkg integrate install
./vcpkg install cppcoroหากเวอร์ชันล้าสมัยโปรดสร้างปัญหาหรือดึงคำขอบนที่เก็บ VCPKG
GitHub issues are the primary mechanism for support, bug reports and feature requests.
Contributions are welcome and pull-requests will be happily reviewed. I only ask that you agree to license any contributions that you make under the MIT license.
If you have general questions about C++ coroutines, you can generally find someone to help in the #coroutines channel on Cpplang Slack group.