EXAMPLES
Coroutine_context example
This is a simple example of using basic coroutine context below:
#include <inttypes.h>
#include <stdint.h>
#include <cstdio>
#include <cstring>
#include <iostream>
// include context header file
#include <libcopp/coroutine/coroutine_context_container.h>
// define a coroutine runner
int my_runner(void *) {
copp::coroutine_context *addr = copp::this_coroutine::get_coroutine();
std::cout << "cortoutine " << addr << " is running." << std::endl;
addr->yield();
std::cout << "cortoutine " << addr << " is resumed." << std::endl;
return 1;
}
int main() {
typedef copp::coroutine_context_default coroutine_type;
// create a coroutine
copp::coroutine_context_default::ptr_t co_obj = coroutine_type::create(my_runner);
std::cout << "cortoutine " << co_obj << " is created." << std::endl;
// start a coroutine
co_obj->start();
// yield from my_runner
std::cout << "cortoutine " << co_obj << " is yield." << std::endl;
co_obj->resume();
std::cout << "cortoutine " << co_obj << " exit and return " << co_obj->get_ret_code() << "." << std::endl;
return 0;
}
Also, you can use copp::coroutine_context_container<ALLOCATOR>
instead of copp::coroutine_context_default
to use a different stack allocator.
Coroutine task example
This is a simple example of using coroutine task with lambda expression:
#include <iostream>
// include task header file
#include <libcotask/task.h>
typedef cotask::task<> my_task_t;
int main() {
// create a task using factory function [with lambda expression]
my_task_t::ptr_t task = my_task_t::create([]() {
std::cout << "task " << cotask::this_task::get<my_task_t>()->get_id() << " started" << std::endl;
cotask::this_task::get_task()->yield();
std::cout << "task " << cotask::this_task::get<my_task_t>()->get_id() << " resumed" << std::endl;
return 0;
});
std::cout << "task " << task->get_id() << " created" << std::endl;
// start a task
task->start();
std::cout << "task " << task->get_id() << " yield" << std::endl;
task->resume();
std::cout << "task " << task->get_id() << " stoped, ready to be destroyed." << std::endl;
return 0;
}
Also, you can your stack allocator or id allocator by setting different parameters in template class cotask::task<TCO_MACRO>
Using coroutine task manager
This is a simple example of using task manager:
#include <inttypes.h>
#include <stdint.h>
#include <cstdio>
#include <cstring>
#include <ctime>
#include <iostream>
// include context header file
#include <libcotask/task.h>
#include <libcotask/task_manager.h>
// create a task manager
typedef cotask::task<> my_task_t;
typedef my_task_t::ptr_t task_ptr_type;
typedef cotask::task_manager<my_task_t> mgr_t;
mgr_t::ptr_t task_mgr = mgr_t::create();
// If you task manager to manage timeout, it's important to call tick interval
void tick() {
// the first parameter is second, and the second is nanosecond
task_mgr->tick(time(nullptr), 0);
}
int main() {
// create two coroutine task
task_ptr_type co_task = my_task_t::create([]() {
std::cout << "task " << cotask::this_task::get<my_task_t>()->get_id() << " started" << std::endl;
cotask::this_task::get_task()->yield();
std::cout << "task " << cotask::this_task::get<my_task_t>()->get_id() << " resumed" << std::endl;
return 0;
});
task_ptr_type co_another_task = my_task_t::create([]() {
std::cout << "task " << cotask::this_task::get<my_task_t>()->get_id() << " started" << std::endl;
cotask::this_task::get_task()->yield();
std::cout << "task " << cotask::this_task::get<my_task_t>()->get_id() << " resumed" << std::endl;
return 0;
});
int res = task_mgr->add_task(co_task, 5, 0); // add task and setup 5s for timeout
if (res < 0) {
std::cerr << "some error: " << res << std::endl;
return res;
}
res = task_mgr->add_task(co_another_task); // add task without timeout
if (res < 0) {
std::cerr << "some error: " << res << std::endl;
return res;
}
res = task_mgr->start(co_task->get_id());
if (res < 0) {
std::cerr << "start task " << co_task->get_id() << " failed, error code: " << res << std::endl;
}
res = task_mgr->start(co_another_task->get_id());
if (res < 0) {
std::cerr << "start task " << co_another_task->get_id() << " failed, error code: " << res << std::endl;
}
res = task_mgr->resume(co_task->get_id());
if (res < 0) {
std::cerr << "resume task " << co_task->get_id() << " failed, error code: " << res << std::endl;
}
res = task_mgr->kill(co_another_task->get_id());
if (res < 0) {
std::cerr << "kill task " << co_another_task->get_id() << " failed, error code: " << res << std::endl;
} else {
std::cout << "kill task " << co_another_task->get_id() << " finished." << std::endl;
}
return 0;
}
Using stack pool
This is a simple example of using stack pool for cotask:
#include <inttypes.h>
#include <stdint.h>
#include <cstdio>
#include <cstring>
#include <ctime>
#include <iostream>
// include context header file
#include <libcopp/stack/stack_pool.h>
#include <libcotask/task.h>
// define the stack pool type
typedef copp::stack_pool<copp::allocator::default_statck_allocator> stack_pool_t;
// define how to create coroutine context
struct sample_macro_coroutine {
using stack_allocator_type = copp::allocator::stack_allocator_pool<stack_pool_t>;
using coroutine_type = copp::coroutine_context_container<stack_allocator_type>;
using value_type = int;
};
// create a stack pool
static stack_pool_t::ptr_t global_stack_pool = stack_pool_t::create();
typedef cotask::task<sample_macro_coroutine> sample_task_t;
int main() {
#if defined(LIBCOTASK_MACRO_ENABLED)
global_stack_pool->set_min_stack_number(4);
std::cout << "stack pool=> used stack number: " << global_stack_pool->get_limit().used_stack_number
<< ", used stack size: " << global_stack_pool->get_limit().used_stack_size
<< ", free stack number: " << global_stack_pool->get_limit().free_stack_number
<< ", free stack size: " << global_stack_pool->get_limit().free_stack_size << std::endl;
// create two coroutine task
{
copp::allocator::stack_allocator_pool<stack_pool_t> alloc(global_stack_pool);
sample_task_t::ptr_t co_task = sample_task_t::create(
[]() {
std::cout << "task " << cotask::this_task::get<sample_task_t>()->get_id() << " started" << std::endl;
cotask::this_task::get_task()->yield();
std::cout << "task " << cotask::this_task::get<sample_task_t>()->get_id() << " resumed" << std::endl;
return 0;
},
alloc);
if (!co_task) {
std::cerr << "create coroutine task with stack pool failed" << std::endl;
return 0;
}
std::cout << "stack pool=> used stack number: " << global_stack_pool->get_limit().used_stack_number
<< ", used stack size: " << global_stack_pool->get_limit().used_stack_size
<< ", free stack number: " << global_stack_pool->get_limit().free_stack_number
<< ", free stack size: " << global_stack_pool->get_limit().free_stack_size << std::endl;
// ..., then do anything you want to do with these tasks
}
std::cout << "stack pool=> used stack number: " << global_stack_pool->get_limit().used_stack_number
<< ", used stack size: " << global_stack_pool->get_limit().used_stack_size
<< ", free stack number: " << global_stack_pool->get_limit().free_stack_number
<< ", free stack size: " << global_stack_pool->get_limit().free_stack_size << std::endl;
{
copp::allocator::stack_allocator_pool<stack_pool_t> alloc(global_stack_pool);
sample_task_t::ptr_t co_another_task = sample_task_t::create(
[]() {
std::cout << "task " << cotask::this_task::get<sample_task_t>()->get_id() << " started" << std::endl;
cotask::this_task::get_task()->yield();
std::cout << "task " << cotask::this_task::get<sample_task_t>()->get_id() << " resumed" << std::endl;
return 0;
},
alloc);
if (!co_another_task) {
std::cerr << "create coroutine task with stack pool failed" << std::endl;
return 0;
}
// ..., then do anything you want to do with these tasks
}
std::cout << "stack pool=> used stack number: " << global_stack_pool->get_limit().used_stack_number
<< ", used stack size: " << global_stack_pool->get_limit().used_stack_size
<< ", free stack number: " << global_stack_pool->get_limit().free_stack_number
<< ", free stack size: " << global_stack_pool->get_limit().free_stack_size << std::endl;
#else
std::cerr << "this sample require cotask enabled." << std::endl;
#endif
return 0;
}
Using then or await_task
This is a simple example of using then
and await_task
for cotask:
/*
* sample_readme_5.cpp
*
* Created on: 2014-05-19
* Author: owent
*
* Released under the MIT license
*/
#include <assert.h>
#include <inttypes.h>
#include <stdint.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <vector>
#include <libcopp/utils/std/explicit_declare.h>
// include manager header file
#include <libcotask/task.h>
#if defined(LIBCOTASK_MACRO_ENABLED)
typedef cotask::task<> my_task_t;
int main() {
int test_code = 128;
// create a task using lambda expression
my_task_t::ptr_t first_task = my_task_t::create([&]() {
puts("|first task running and will be yield ...");
cotask::this_task::get_task()->yield();
puts("|first task resumed ...");
printf("test code already reset => %d\n", ++test_code);
});
// add many then task using lambda expression
first_task
->then([=]() {
puts("|second task running...");
printf("test code should be inited 128 => %d\n", test_code);
})
->then([&]() {
puts("|haha ... this is the third task.");
printf("test code is the same => %d\n", ++test_code);
return "return value will be ignored";
})
->then(
[&](EXPLICIT_UNUSED_ATTR void *priv_data) {
puts("|it's boring");
printf("test code is %d\n", ++test_code);
assert(&test_code == priv_data);
return 0;
},
&test_code);
test_code = 0;
// start a task
first_task->start();
first_task->resume();
// these code below will failed.
first_task->then([]() {
puts("this will run immediately.");
return 0;
});
my_task_t::ptr_t await_task = my_task_t::create([&]() {
puts("await_task for first_task.");
return 0;
});
await_task->await_task(first_task);
printf("|task start twice will failed: %d\n", first_task->start());
printf("|test_code end with %d\n", test_code);
return 0;
}
#else
int main() {
puts("this sample require cotask enabled");
return 0;
}
#endif
Using c++20 coroutine
// Copyright 2023 owent
// Created by owent on 2022-05-27
#include <iostream>
// include manager header file
#include <libcopp/coroutine/callable_promise.h>
#if defined(LIBCOPP_MACRO_ENABLE_STD_COROUTINE) && LIBCOPP_MACRO_ENABLE_STD_COROUTINE
static copp::callable_future<int> coroutine_callable_with_int_result() {
// ... any code
co_return 123;
}
static copp::callable_future<void> coroutine_callable_with_void_result() {
// ... any code
co_return;
}
static copp::callable_future<void> coroutine_simulator_task() {
// suspend and wait custom waker
(void)co_await LIBCOPP_MACRO_STD_COROUTINE_NAMESPACE suspend_always();
// ... any code
// We can get current status by co_yield yield_status()
auto current_status = co_yield copp::callable_future<int>::yield_status();
// The return value will be ignored when the future is already set by custom waker
std::cout << "Current coroutine callable status: " << static_cast<uint32_t>(current_status) << std::endl;
co_await coroutine_callable_with_void_result();
int result = co_await coroutine_callable_with_int_result();
std::cout << "Coroutine int callable result: " << result << std::endl;
co_return;
}
int main() {
auto rpc_result = coroutine_simulator_task();
// We should not explict call start and get_internal_handle().resume() in a real usage
// It's only allowed to start and resume by co_wait the callable_future object
rpc_result.get_internal_handle().resume(); // resume co_await LIBCOPP_MACRO_STD_COROUTINE_NAMESPACE suspend_always();
std::cout << "Current coroutine callable status: " << static_cast<uint32_t>(rpc_result.get_status()) << std::endl;
return 0;
}
#else
int main() {
puts("this sample require cotask enabled and compiler support c++20 coroutine");
return 0;
}
#endif
Using c++20 coroutine with custom generator
/*
* sample_readme_8.cpp
*
* Created on: 2020-05-22
* Author: owent
*
* Released under the MIT license
*/
#include <assert.h>
#include <iostream>
#include <string>
// include manager header file
#include <libcopp/coroutine/callable_promise.h>
#include <libcopp/coroutine/generator_promise.h>
#if defined(LIBCOPP_MACRO_ENABLE_STD_COROUTINE) && LIBCOPP_MACRO_ENABLE_STD_COROUTINE
using my_generator = copp::generator_future<int>;
std::list<my_generator::context_pointer_type> g_sample_executor;
static void generator_callback(my_generator::context_pointer_type ctx) {
g_sample_executor.emplace_back(std::move(ctx));
}
static copp::callable_future<void> coroutine_simulator_rpc() {
my_generator generator_object{generator_callback};
auto value1 = co_await generator_object;
std::cout << "co_await named generator: " << value1 << std::endl;
auto value2 = co_await my_generator{generator_callback};
std::cout << "co_await temporary generator: " << value2 << std::endl;
generator_object.get_context()->reset_value();
auto value3 = co_await generator_object;
std::cout << "reset and co_await named generator again: " << value3 << std::endl;
co_return;
}
int main() {
int result = 191;
auto f = coroutine_simulator_rpc();
while (!g_sample_executor.empty()) {
auto ctx = g_sample_executor.front();
g_sample_executor.pop_front();
ctx->set_value(++result);
}
return 0;
}
#else
int main() {
puts("this sample require cotask enabled and compiler support c++20 coroutine");
return 0;
}
#endif
Custom error (timeout for example) for c++20 coroutine
By implementing std_coroutine_default_error_transform<CustomType>
, we can transform error code of libcopp to our custom type.
/*
* sample_readme_9.cpp
*
* Created on: 2020-05-20
* Author: owent
*
* Released under the MIT license
*/
#include <iostream>
// include manager header file
#include <libcopp/coroutine/callable_promise.h>
#include <libcopp/coroutine/generator_promise.h>
#if defined(LIBCOPP_MACRO_ENABLE_STD_COROUTINE) && LIBCOPP_MACRO_ENABLE_STD_COROUTINE
// ============================ types for task and generator ============================
class sample_message_t {
public:
int ret_code;
sample_message_t() : ret_code(0) {}
sample_message_t(int c) : ret_code(c) {}
sample_message_t(const sample_message_t &) = default;
sample_message_t &operator=(const sample_message_t &) = default;
sample_message_t(sample_message_t &&) = default;
sample_message_t &operator=(sample_message_t &&) = default;
~sample_message_t() {}
};
# define SAMPLE_TIMEOUT_ERROR_CODE (-500)
LIBCOPP_COPP_NAMESPACE_BEGIN
template <>
struct std_coroutine_default_error_transform<sample_message_t> {
using type = sample_message_t;
type operator()(promise_status in) const {
if (in == promise_status::kTimeout) {
return sample_message_t{SAMPLE_TIMEOUT_ERROR_CODE};
}
return sample_message_t{static_cast<int>(in)};
}
};
LIBCOPP_COPP_NAMESPACE_END
using int_generator = copp::generator_future<int>;
std::list<int_generator::context_pointer_type> g_int_executor;
using custom_generator = copp::generator_future<sample_message_t>;
std::list<custom_generator::context_pointer_type> g_sample_executor;
static void int_generator_callback(int_generator::context_pointer_type ctx) {
g_int_executor.emplace_back(std::move(ctx));
}
static void custom_generator_callback(custom_generator::context_pointer_type ctx) {
g_sample_executor.emplace_back(std::move(ctx));
}
static copp::callable_future<int> coroutine_simulator_rpc_integer_l2() {
auto result = co_await int_generator{int_generator_callback};
co_return result;
}
static copp::callable_future<void> coroutine_simulator_rpc_integer() {
auto result = co_await coroutine_simulator_rpc_integer_l2();
std::cout << "int generator is killed with code: " << result << std::endl;
co_return;
}
static copp::callable_future<int> coroutine_simulator_rpc_custom_l2() {
auto result = co_await custom_generator{custom_generator_callback};
co_return result.ret_code;
}
static copp::callable_future<void> coroutine_simulator_rpc_custom() {
auto result = co_await coroutine_simulator_rpc_custom_l2();
std::cout << "custom generator is killed with code: " << result << std::endl;
co_return;
}
int main() {
// sample for await generator and timeout
auto f1 = coroutine_simulator_rpc_integer();
f1.kill(copp::promise_status::kCancle, true);
std::cout << "int generator is killed" << std::endl;
// sample for await task and timeout
auto f2 = coroutine_simulator_rpc_custom();
f2.kill(copp::promise_status::kTimeout, true);
std::cout << "custom generator is killed" << std::endl;
return 0;
}
#else
int main() {
puts("this sample require cotask enabled and compiler support c++20 coroutine");
return 0;
}
#endif
Let c++20 coroutine work with cotask::task<MACRO>
This is a simple example to let c++20 coroutine await cotask::task
/*
* sample_readme_10.cpp
*
* Created on: 2020-05-20
* Author: owent
*
* Released under the MIT license
*/
#include <iostream>
// include manager header file
#include <libcopp/coroutine/callable_promise.h>
#include <libcotask/task.h>
#if defined(LIBCOPP_MACRO_ENABLE_STD_COROUTINE) && LIBCOPP_MACRO_ENABLE_STD_COROUTINE
typedef cotask::task<> my_task_t;
static copp::callable_future<int> call_for_await_cotask(my_task_t::ptr_t t) {
if (t) {
auto ret = co_await t;
co_return ret;
}
co_return 0;
}
static int cotask_action_callback(void*) {
int ret = 234;
void* ptr = nullptr;
cotask::this_task::get_task()->yield(&ptr);
if (ptr != nullptr) {
ret = *reinterpret_cast<int*>(ptr);
}
return ret;
}
int main() {
my_task_t::ptr_t co_task = my_task_t::create(cotask_action_callback);
auto t = call_for_await_cotask(co_task);
co_task->start();
int res = 345;
co_task->resume(reinterpret_cast<void*>(&res));
std::cout << "co_await a cotask::task and get result: " << t.get_internal_promise().data() << std::endl;
return 0;
}
#else
int main() {
puts("this sample require cotask enabled and compiler support c++20 coroutine");
return 0;
}
#endif
Using SetUnhandledExceptionFilter
on Windows with cotask::task<MACRO>
Some applications will use SetUnhandledExceptionFilter
to catch unhandled exception and analysis crash problem. But SetUnhandledExceptionFilter
is only works with coroutine context of windows fiber . This is a sample of using windows fiber as coroutine context in cotask::task<MACRO>
.
#include <iostream>
#include <libcopp/utils/config/libcopp_build_features.h>
#if (defined(LIBCOTASK_MACRO_ENABLED) && LIBCOTASK_MACRO_ENABLED) && defined(LIBCOPP_MACRO_ENABLE_WIN_FIBER) && \
LIBCOPP_MACRO_ENABLE_WIN_FIBER
// include task header file
# include <libcotask/task.h>
struct my_task_macro_t {
using stack_allocator_type = copp::coroutine_fiber_context_default::allocator_type;
using coroutine_type = copp::coroutine_fiber_context_default;
using value_type = int;
};
typedef cotask::task<my_task_macro_t> my_task_t;
# ifdef _MSC_VER
# pragma warning(push)
# pragma warning(disable : 4091)
# include <atlconv.h>
# include <imagehlp.h>
# pragma comment(lib, "dbghelp.lib")
# ifdef UNICODE
# define SAMPLE_VC_TEXT(x) A2W(x)
# else
# define SAMPLE_VC_TEXT(x) x
# endif
LPTOP_LEVEL_EXCEPTION_FILTER g_msvc_debuger_old_handle = nullptr;
std::string g_msvc_debuger_pattern;
inline void CreateMiniDump(EXCEPTION_POINTERS *pep, LPCTSTR strFileName) {
HANDLE hFile =
CreateFile(strFileName, GENERIC_READ | GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
if ((hFile != nullptr) && (hFile != INVALID_HANDLE_VALUE)) {
MINIDUMP_EXCEPTION_INFORMATION mdei;
mdei.ThreadId = GetCurrentThreadId();
mdei.ExceptionPointers = pep;
mdei.ClientPointers = FALSE;
// MINIDUMP_CALLBACK_INFORMATION mci;
// mci.CallbackRoutine = (MINIDUMP_CALLBACK_ROUTINE)MiniDumpCallback;
// mci.CallbackParam = 0;
MINIDUMP_TYPE mdt =
(MINIDUMP_TYPE)(MiniDumpWithPrivateReadWriteMemory | MiniDumpWithDataSegs | MiniDumpWithHandleData |
MiniDumpWithFullMemoryInfo | MiniDumpWithThreadInfo | MiniDumpWithUnloadedModules);
MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, mdt, (pep != 0) ? &mdei : 0, 0, nullptr);
CloseHandle(hFile);
}
}
LONG WINAPI GPTUnhandledExceptionFilter(PEXCEPTION_POINTERS pExceptionInfo) {
//得到当前时间
SYSTEMTIME st;
::GetLocalTime(&st);
//得到程序所在文件夹
// TCHAR exeFullPath[256]; // MAX_PATH
// GetModuleFileName(nullptr, exeFullPath, 256);//得到程序模块名称,全路径
TCHAR szFileName[_MAX_FNAME] = {0};
USES_CONVERSION;
wsprintf(szFileName, TEXT("%s-%04d-%02d-%02d.%02d%02d%02d.%03d.dmp"), SAMPLE_VC_TEXT(g_msvc_debuger_pattern.c_str()),
st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
CreateMiniDump(pExceptionInfo, szFileName);
if (nullptr == g_msvc_debuger_old_handle) {
return EXCEPTION_EXECUTE_HANDLER; // 下一个Handle, 一般是程序停止运行
}
return g_msvc_debuger_old_handle(pExceptionInfo);
}
void __cdecl sample_setup_msvc_mini_dump(const char *prefix) {
g_msvc_debuger_pattern = prefix;
g_msvc_debuger_old_handle = SetUnhandledExceptionFilter(GPTUnhandledExceptionFilter);
if (g_msvc_debuger_old_handle == GPTUnhandledExceptionFilter) {
g_msvc_debuger_old_handle = nullptr;
}
}
# endif
int main() {
# ifdef _MSC_VER
sample_setup_msvc_mini_dump("d:/libcopp-test-minidump");
# endif
// create a task using factory function [with lambda expression]
my_task_t::ptr_t task = my_task_t::create([]() {
std::cout << "task " << cotask::this_task::get<my_task_t>()->get_id() << " started" << std::endl;
cotask::this_task::get_task()->yield();
std::cout << "task " << cotask::this_task::get<my_task_t>()->get_id() << " resumed" << std::endl;
// ! Make crash and it's will generate a mini dump into d:/libcopp-test-minidump-*.dmp
// copp::this_coroutine::get_coroutine()->yield();
return 0;
});
std::cout << "task " << task->get_id() << " created" << std::endl;
// start a task
task->start();
std::cout << "task " << task->get_id() << " yield" << std::endl;
task->resume();
std::cout << "task " << task->get_id() << " stoped, ready to be destroyed." << std::endl;
return 0;
}
# ifdef _MSC_VER
# pragma warning(pop)
# endif
#else
int main() {
std::cerr << "lambda not supported, or fiber is not supported, this sample is not available." << std::endl;
return 0;
}
#endif