admin管理员组

文章数量:1400161

Basically, the whole idea is something like the Task implementation in C# where functions can return Task<T> that callers can await on. For some reason, TestFuncAsync runs correctly the first 2 times, and then craps out on the third on the DelayAwaiter. For some strange reason, the handle value in the DelayAwaiter on the third iteration is the same as the second iteration, and since it is already finished, it craps out.

Main code:

//basic IO function, run 5 times
//works wonderfully twice, craps out on await DelayAwaiter on the third time
Task<int> TestFuncAsync (int a, int b)
{
    std::cout << "Simulating device I/O" << std::endl;

    std::thread::id thread_id = co_await DelayAwaiter {};

    std::cout << "This thread: " << std::this_thread::get_id () << " Other thread: " << thread_id << std::endl
        ;// << std::stacktrace::current () << std::endl;

    co_return a + b;
}

//parent coro
Task<int> TestFunc2Async (int a)
{
    int acc = 0;

    for (size_t i = 0; i < a; i++)
    {
        acc += co_await TestFuncAsync (i, a);
    }

    co_return acc;
}

//probably set up wrong anyway, dont comment on this
std::mutex lock;
std::condition_variable cv;

Task<int> Main ()
{
    int test = co_await TestFunc2Async (5);

    //cv.notify_all ();

    co_return test;
}


export int main ()
{
    var mainTask = Main ();

    std::unique_lock lockGuard (lock);

    cv.wait (lockGuard);

    //wait for the whole thing to finish
    std::this_thread::sleep_for (std::chrono::seconds (50));

    std::cout << mainTask.GetResult ();
}

Task class:

    template <typename T = void>
    struct Task
    {
    public:
        struct promise_type
        {
            std::optional<T> result;

            //so when this coroutine is finished, the parent can be resumed
            std::optional<std::coroutine_handle<>> parentCoroutine;

            Task get_return_object ()
            {
                return Task { std::coroutine_handle<promise_type>::from_promise (*this) };
            }

            std::suspend_never initial_suspend () noexcept { 
                std::cout << "Coroutine " << std::coroutine_handle<promise_type>::from_promise (*this).address () << " started" << std::endl;
                return {}; }
            std::suspend_always final_suspend () noexcept
            {
                //resume parent coroutine when this is done,
                //is this horrible code, probably. but I cant think of any other way without making a whole scheduler
                if (parentCoroutine.has_value ())
                {
                    std::cout << "Coroutine " << std::coroutine_handle<promise_type>::from_promise (*this).address () << " completed; parent coroutine " << parentCoroutine.value ().address () << " resuming" << std::endl;
                    std::coroutine_handle<> handle = parentCoroutine.value ();
                    parentCoroutine.reset ();
                    handle.resume ();
                }
                return {};
            }

            void return_value (T value) { result = std::move (value); }
            void unhandled_exception ()
            {//for debugging purposes
                auto e = std::current_exception ();
                try
                {
                    std::rethrow_exception (e);
                }
                catch (const std::exception& ex)
                {
                    std::cout << ex.what ();
                }
                std::terminate ();
            }
        };

        std::coroutine_handle<promise_type> handle;

    public:
        struct Awaiter
        {
            std::coroutine_handle<promise_type> handle;
            std::coroutine_handle<> calling_coroutine;
            bool completed = false;

            bool await_ready () { return false; }  // Always suspend
            bool await_suspend (std::coroutine_handle<> calling_coroutine)
            {
                std::cout << "Coroutine " << handle.address () << " has suspended coroutine " << calling_coroutine.address () << std::endl;
                handle.promise ().parentCoroutine = calling_coroutine;
                this->calling_coroutine = calling_coroutine;
                return true;
            }
            T await_resume ()
            {
                std::cout << "Coroutine " << calling_coroutine.address () << " has been resumed" << std::endl;
                return std::move (handle.promise ().result.value ());
            }
        };

        Task (std::coroutine_handle<promise_type> h) : handle (h) {}
        ~Task ()
        {
            if (handle) handle.destroy ();
        }
        Task (const Task<T>&) = delete;
        Task operator=(const Task) = delete;

        T&& GetResult ()
        {
            return std::move (handle.promise ().result.value ());
        }

        Awaiter operator co_await() { return Awaiter { handle }; }
    };

    //this is my best guess for how a callback awaiter is supposed to work.
    struct DelayAwaiter
    {
        //just information that is printed, used to see if it is actually running on another thread or not
        std::thread::id thread_id;

        constexpr bool await_ready () const noexcept
        {
            return false;
        }

        bool await_suspend (std::coroutine_handle<> handle)
        {
            std::cout << "DelayAwaiter suspending coroutine " << handle.address () << std::endl;
            //from debugging, it seems that 1st and 2nd iterations, handle is correct, but the third iteration,
            //it uses the handle from the 2nd iteration?
            auto thread = std::thread ([this] (std::coroutine_handle<> handle)
                                       {
                                           thread_id = std::this_thread::get_id ();
                                           std::this_thread::sleep_for (std::chrono::seconds (1));//fake delay
                                           std::cout << "DelayAwaiter resuming coroutine " << handle.address () << std::endl;
                                           //this is where the program craps out on the third time, handle is already completed.
                                           handle.resume ();//resume the coro on the "callback" thread
                                       }, handle);
            thread.detach ();
            return true;//always suspend coroutine chain
        }
        constexpr std::thread::id await_resume () const noexcept
        {
            return thread_id;//return value that is printed to console
        }
    };

Output:

Coroutine 00000211DE5D0000 started//Main()
Coroutine 00000211DE5D0140 started//TestFuncAsync2()
Coroutine 00000211DE5D0280 started//TestFuncAsync()
Simulating device I/O
DelayAwaiter suspending coroutine 00000211DE5D0280
Coroutine 00000211DE5D0280 has suspended coroutine 00000211DE5D0140
Coroutine 00000211DE5D0140 has suspended coroutine 00000211DE5D0000
DelayAwaiter resuming coroutine 00000211DE5D0280
This thread: 28080 Other thread: 28080
Coroutine 00000211DE5D0280 completed; parent coroutine 00000211DE5D0140 resuming
Coroutine 00000211DE5D0140 has been resumed
Coroutine 00000211DE600000 started
Simulating device I/O
DelayAwaiter suspending coroutine 00000211DE600000
Coroutine 00000211DE600000 has suspended coroutine 00000211DE5D0140
DelayAwaiter resuming coroutine 00000211DE600000
This thread: 61828 Other thread: 61828
Coroutine 00000211DE600000 completed; parent coroutine 00000211DE5D0140 resuming
Coroutine 00000211DE5D0140 has been resumed
Coroutine 00000211DE600000 started
Simulating device I/O
DelayAwaiter suspending coroutine 00000211DE600000
Coroutine 00000211DE600000 has suspended coroutine 00000211DE5D0140
DelayAwaiter resuming coroutine 00000211DE600000

D:\MasterpieceV2\IXX\x64\Debug\Core.exe (process 111012) exited with code -1 (0xffffffff).

Judging from the logs, it seems that coroutine 00000211DE600000 (TestFuncAsync() 2nd iteration) starts and completes normally, but on the next iteration, the coroutine is reused, calls initial_suspend() and then believes that it is on the final suspend point skipping the body of the function after the co_await statement.

EDIT 1: Added more detailed logs.

本文标签: asynchronousC Coroutine task class strange issueStack Overflow