如何将C++协程异常传播回调用者?

huangapple go评论55阅读模式
英文:

How to propagate C++ coroutine exceptions back to the caller?

问题

我想要调用协程的调用者在协程体内发生的所有异常在调用协程时立即接收到。这是否可能?这是我拥有的:

[演示][1]

#include <iostream>
#include <exception>
#include <coroutine>
#include <string_view>

struct MyCoroutine;

struct promise_type {
    std::exception_ptr exception_;
    std::string_view str_frag_;

    auto get_return_object() -> MyCoroutine;

    std::suspend_never initial_suspend() noexcept { return {}; }
    std::suspend_always final_suspend() noexcept { return {}; }

    void return_void() {}

    void unhandled_exception() {
        exception_ = std::current_exception();
    }

};

struct MyCoroutine {
    using promise_type = ::promise_type;

    std::coroutine_handle<promise_type> coroutine;

    MyCoroutine(std::coroutine_handle<promise_type> handle) : coroutine(handle) {}
    ~MyCoroutine() {
        if (coroutine)
            coroutine.destroy();
    }

    void resume(std::string_view str_frag) {
        if (!coroutine.done()) {
            coroutine.promise().str_frag_ = str_frag;
            coroutine.resume();
            if (coroutine.promise().exception_)
                std::rethrow_exception(coroutine.promise().exception_);
        }
    }
};

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

struct awaitable {
    std::coroutine_handle<promise_type> handle_;
    auto await_ready() -> bool { return false; }
    auto await_suspend(std::coroutine_handle<promise_type> h) -> void { handle_ = h; }
    auto await_resume() -> std::string_view { return handle_.promise().str_frag_; }
};

MyCoroutine myCoroutineFunction(std::string_view mystr) {
    std::cout << "协程以 mystr = " << mystr << " 启动了" << std::endl;

    // 与 mystr 做一些工作..

    mystr = co_await awaitable{};
    std::cout << "协程以 mystr = " << mystr << " 继续了" << std::endl;

    // 与 mystr 做更多的工作..

    throw std::runtime_error("在协程中抛出了异常。");
    std::cout << "协程完成。" << std::endl;
    co_return;
}

int main() {
    MyCoroutine coroutine = myCoroutineFunction("你好");
    try {
        coroutine.resume("世界");
    } catch (const std::exception& e) {
        std::cout << "在调用者上下文中捕获到异常:" << e.what() << std::endl;
    }
    return 0;
}

这产生了:

协程以 mystr = 你好 启动了
协程以 mystr = 世界 继续了
在调用者上下文中捕获到异常:在协程中抛出了异常。

问题:正如你从上面的代码中看到的,我只能从返回对象的 `resume()` 函数中重新抛出异常。但我无法重新抛出在第一次调用 `resume()` 之前可能发生的异常,例如从协程开始直到第一次暂停点。这意味着我不能做任何有意义的工作,而不让用户想知道是否实际上引发了异常,因为直到此时异常才必须手动检索。

我唯一想到的解决方案是始终在开始时暂停,并使协程不接受任何参数,因此只是用于初始化协程函数,并使用第一次 `resume()` 调用实际开始它。我认为这真的是一个笨拙的设计,我有什么遗漏吗?
[1]: https://godbolt.org/#z:OYLghAFBqd5QCxAYwPYBMCmBRdBLAF1QCcAaPECAMzwBtMA7AQwFtMQByARg9KtQYEAysib0QXACx8BBAKoBnTAAUAHpwAMvAFYTStJg1DIApACYAQuYukl9ZATwDKjdAGFUtAK4sGe1wAyeAyYAHI%2BAEaYxCAAHFykAA6oCoRODB7evnrJqY4CQSHhLFEx8baY9vkMQgRMxASZPn4JdpgO6bX1BIVhkdF6CnUNTdmtwz3BfSUDXACUtqhexMjsHOYAzMHI3lgA1CYbbk5DxJish9gmGgCCm9u7mAdHmKqridWX13dmWww7Xn2hzcaGIS0cIS%2Bt3u/0ezzcp2CwAA%2BgA3PCYADuULut1OXgcewAsgBPDxgrwQzCHKzQvEEYgEgh7RJglh4JTIggkxJPEwAdlpNz2Ir2Q3QIBAr3e1WRH2Ie2lmA%2B6WRNO%2BorFBAlIERRjRGMxWuIyKoxCYKPVdOFoqYlNQe2AmAIyLOBGWDGRqAi2naBAgcz2AFpLsSySRwVMrbibSLxZKFF4FLyGOhkSFUdE9sE0mJkYnk64A3sGKglR8DoK9m6PZWrPyACI0ytN26a%2BO6pMptNiTFMEkKPY0Zi0fNdouB0vl5kCizV5212cCpsbOfL74a0Wo1B4dDz93ET3b3fFpeNjdtrc7vdeBgIQzoehp6fpU%2BCzeakUvgTI54NrU6sgyxnIIyLfgwAbRpq67Wuuq4Xjc%2BKEqS5KRiEdYfnsSZIiybIcpgXI8nyGz/pKrKoOynLcry0aYR2oJoQR96pvQwLkZRBHUdSGzYHsDGUlG8HWpqKERgJIQQPRYlUsizGPtxbjsfhhE0TxexyfQgYgHx0lTBAGmYIGZ

<details>
<summary>英文:</summary>

I want the caller who issues the start/continuation of the coroutine to receive all exceptions thrown in the coroutine body right upon invocation of the coroutine. Is this even possible? Here&#39;s what I have:

[Demo][1]

    #include &lt;iostream&gt;
    #include &lt;exception&gt;
    #include &lt;coroutine&gt;
    #include &lt;string_view&gt;
    
    struct MyCoroutine;
    
    struct promise_type {
        std::exception_ptr exception_;
        std::string_view str_frag_;
    
        auto get_return_object() -&gt; MyCoroutine;
    
        std::suspend_never initial_suspend() noexcept { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
    
        void return_void() {}
    
        void unhandled_exception() {
            exception_ = std::current_exception();
        }
    
    };
    
    struct MyCoroutine {
        using promise_type = ::promise_type;
    
        std::coroutine_handle&lt;promise_type&gt; coroutine;
    
        MyCoroutine(std::coroutine_handle&lt;promise_type&gt; handle) : coroutine(handle) {}
        ~MyCoroutine() {
            if (coroutine)
                coroutine.destroy();
        }
    
        void resume(std::string_view str_frag) {
            if (!coroutine.done()) {
                coroutine.promise().str_frag_ = str_frag;
                coroutine.resume();
                if (coroutine.promise().exception_)
                    std::rethrow_exception(coroutine.promise().exception_);
            }
        }
    };
    
    auto promise_type::get_return_object() -&gt; MyCoroutine {
        return { std::coroutine_handle&lt;promise_type&gt;::from_promise(*this) };
    }
    
    struct awaitable {
        std::coroutine_handle&lt;promise_type&gt; handle_;
        auto await_ready() -&gt; bool { return false; }
        auto await_suspend(std::coroutine_handle&lt;promise_type&gt; h) -&gt; void { handle_ = h; }
        auto await_resume() -&gt; std::string_view { return handle_.promise().str_frag_; }
    };
    
    MyCoroutine myCoroutineFunction(std::string_view mystr) {
        std::cout &lt;&lt; &quot;Coroutine started with mystr = &quot; &lt;&lt; mystr &lt;&lt; std::endl;
    
        // Doing some work with mystr..
    
        mystr = co_await awaitable{};
        std::cout &lt;&lt; &quot;Coroutine continued with mystr = &quot; &lt;&lt; mystr &lt;&lt; std::endl;
    
        // Doing some more work with mystr..
    
        throw std::runtime_error(&quot;Exception thrown in the coroutine.&quot;);
        std::cout &lt;&lt; &quot;Coroutine completed.&quot; &lt;&lt; std::endl;
        co_return;
    }
    
    int main() {
        MyCoroutine coroutine = myCoroutineFunction(&quot;Hello&quot;);
        try {
            coroutine.resume(&quot;World&quot;);
        } catch (const std::exception&amp; e) {
            std::cout &lt;&lt; &quot;Caught exception in the caller context: &quot; &lt;&lt; e.what() &lt;&lt; std::endl;
        }
        return 0;
    }

   

This yields

    Coroutine started with mystr = Hello
    Coroutine continued with mystr = World
    Caught exception in the caller context: Exception thrown in the coroutine.

**Problem**: As you can see from above code, I&#39;m only able to rethrow the exception from the return object&#39;s `resume()` function. But I&#39;m not able to rethrow an exception that might happen BEFORE the resume function was first called, e.g. from the beginning of the coroutine until its first suspension point. This means I can&#39;t do any meaningful work without leaving the user wondering if that actually caused an exception, as the exception would have to be retrieved manually until this point.

The only solution I can think of is to always suspend at the beginning and have the coroutine not take any arguments, so use the coroutine function just for initialization and actually start it using the first `resume()` invocation. I think that is really awkward design, am I missing something?


  [1]: https://godbolt.org/#z:OYLghAFBqd5QCxAYwPYBMCmBRdBLAF1QCcAaPECAMzwBtMA7AQwFtMQByARg9KtQYEAysib0QXACx8BBAKoBnTAAUAHpwAMvAFYTStJg1DIApACYAQuYukl9ZATwDKjdAGFUtAK4sGe1wAyeAyYAHI%2BAEaYxCAAHFykAA6oCoRODB7evnrJqY4CQSHhLFEx8baY9vkMQgRMxASZPn4JdpgO6bX1BIVhkdF6CnUNTdmtwz3BfSUDXACUtqhexMjsHOYAzMHI3lgA1CYbbk5DxJish9gmGgCCm9u7mAdHmKqridWX13dmWww7Xn2hzcaGIS0cIS%2Bt3u/0ezzcp2CwAA%2BgA3PCYADuULut1OXgcewAsgBPDxgrwQzCHKzQvEEYgEgh7RJglh4JTIggkxJPEwAdlpNz2Ir2Q3QIBAr3e1WRH2Ie2lmA%2B6WRNO%2BorFBAlIERRjRGMxWuIyKoxCYKPVdOFoqYlNQe2AmAIyLOBGWDGRqAi2naBAgcz2AFpLsSySRwVMrbibSLxZKFF4FLyGOhkSFUdE9sE0mJkYnk64A3sGKglR8DoK9m6PZWrPyACI0ytN26a%2BO6pMptNiTFMEkKPY0Zi0fNdouB0vl5kCizV5212cCpsbOfL74a0Wo1B4dDz93ET3b3fFpeNjdtrc7vdeBgIQzoehp6fpU%2BCzeakUvgTI54NrU6sgyxnIIyLfgwAbRpq67Wuuq4Xjc%2BKEqS5KRiEdYfnsSZIiybIcpgXI8nyGz/pKrKoOynLcry0aYR2oJoQR96pvQwLkZRBHUdSGzYHsDGUlG8HWpqKERgJIQQPRYlUsizGPtxbjsfhhE0TxexyfQgYgHx0lTBAGmYIGZ6trGewAH6iRSVJvkKn6ingVB7BA/FUnMmF2SKLlTAAdFgpyoCSkFCaZMExpqx57mciZsJJ2oJgySIGlixqmuawBGe%2Bl52Q5TlgGAXkhL5AiYAGGW2R5moFZg3lKUoAbeacqUWr%2Bhz/o1ZoWlBFWilV3lRT4JVucF3X2Y5zm6YVtWDd54HIm5WUjaKHZuggYKYmBbzKtU41WT5U31bNQ3lXZoXQee0LnsF3x2kQuEUcpXGSk6Lo1oeXo%2Bn6xYhmplmMRhC2vQwdYAZKVWyQ%2BrFHFNKncdgkpmhRcp4XVABUBAIByGUrkKoXfEhzJMH2hBMBE9D/aZUm7SE4MsQp0NcaGBlqsNtr2nshNMIQrrnOggWBt9vERKgnjA4DQ5iEozanazt0c1zBbdrFgETUxEN08jnFEYz/OhhFwNM3%2B6lS%2Bdpk3Q6csvZg0WDcGoYdnqKLosls77rWTM1Rr9XtWlzNribcE47cv3iU8LDhlTmAAGK3h0AhK/FxCJU7Rph6cZV0XFKDgvCwIHGYZioSHWrdJge6YoQCB7KnDKG%2BYZg50cVcDjXwK5x2ri0LRC0APTd3sDY7kYYoUU8mIkAA1ns5fo03pzed5CGatXCqtTpyIW%2BzRN1KT1Lvpdx0g1nlIN24ecFyrOmCMEXil1PFezy3JFnyfD8r0cbeZx3Xemb3/eD8Aw82BVxIKPCed8Z7L3novUU6M1qH0ZFfNgYFiBgmIBAOu2BNoqgEHsWBqBMRA2CLghATxep1yOhnZWx9W6NzroXKkOkWCJHoAQUuC984v3bixLqnlUDcwPAwWi/tbjBGZCwTmEF04LWDgwqqhsw70KmNHf42064AAlKi0FQOQnhuDiAknJhVXq/UYp1wAOokFoOgHRLMRTLj4kwAgyBK7jQYEMQ%2B4FzAADZFRSNMu2TOaBqHv1ofnNwdpgAIGZOBbMQN0akLEPQBUaBBCvAINpOuL9qqYnvP6IyITT5cMfLo6WIoxYaCESZDgCxaCcAAKy8D8BwLQpBUCcDcNYaww9lirDzhsHgpB0nNOqQsceIA6kaH0JwSQjTNC8DaRwXgCgQCTKGVoBYcBYAwEQFnJhdBojkEoGgPZSSQDAC4P00gWB0SrAAGqGgAPIpk4AMmgtBWHEGWRACIczSARGCPUEkLzeD/OYPoh5H0HDAtIMctgggHkMFoEC4ZVzMARC8MAcJtBaDLO4LwLA4ijDiBRfgM4HRMy4paa8dolI1gtNEZUX5tA8ARHNPojwWBfkJRYMChYVADDAAUPcrETzGDQv4IIEQYh2BSBkIIRQKh1Aot0AkAwRgUCdMsPoFlyzIALFQNgtxnAgzilaqYSw1gzAaGDA8hQ8zMwoN3FbeACw2ixz8BAVwow/AbDqaQQIUxiilBAPyJIKQ0gCG9SAX1Ya8jpF6EGgYoa3XVC6CMTwzRo1%2BpTZ0CYCb%2BgxGTRMKNMahjdHzTMQtrqlgrBlTU%2BpsyUULL2KoWIXigxeMkI6ZAyA9gXO8lwJyuBCAkD6fMXgayRmkDGRMqZHAZmkB5XUyQ3lJAAE5%2BRro0Ju9dm7t1rtIE0lpCylkrMGXMjZ2z9IgHBIkSkhzxonOiKEVgaxW3ts7d23t/aBmlxHYnCUCQJXCFEOIWVwGFVqF%2BSq0gmJzSJF5XOhph7fkLIeZSO9zJUCOXfR2rtwAe19o2AOpyHgn0r1%2BOO89wy5gLBIUwLAMQAxzoXTyyQdTvITKtWuq1XGNA8YSEe%2BZnBT2rIvaM8ZkzakcA2I249InqPrPrRwMwcnhOLMU1Oh1qRnCSCAA%3D

</details>


# 答案1
**得分**: 1

你的协程在初始暂停点不会挂起。它也不会在最终暂停点挂起。它也从不执行 `co_await`  `co_yield`。这意味着它实际上从不挂起。因此,它会在 `main` 看到它之前完成。

这是不好的,因为当调用 `coroutine.resume()` 时,[句柄的协程状态已被销毁][1]。如果达到最终暂停点但不在那里挂起,就会发生这种情况。因此,对 `coroutine_handle` 上的任何函数(如调用 `done()`  `resume()`)都会导致未定义行为。

如果您希望协程将 *任何东西* 转发回其未来对象的所有者(协程的返回值),*必须* 至少具有最终挂起点。

  [1]: https://stackoverflow.com/a/74424079/734069

<details>
<summary>英文:</summary>

Your coroutine does not suspend at the initial suspend point. It also does not suspend at the final suspend point. And it never `co_await`s or `co_yield`s. This means that it never actually suspends. So it completes before `main` ever sees it.

This is bad because, by the time `coroutine.resume()` gets called, [the coroutine state for the handle has been destroyed][1]. That&#39;s what happens if you reach the final suspend point and don&#39;t suspend there. So using any function on the `coroutine_handle` (like calling `done()` or `resume()`) is UB.

If you want a coroutine to forward *anything* back to the owner of its future object (the return value of the coroutine), it *must* at least have a final suspend point.


  [1]: https://stackoverflow.com/a/74424079/734069

</details>



# 答案2
**得分**: 1

你可以在你的promise_type中使用return_void函数,类似于这样:

```cpp
void return_void() const
{
    if (std::holds_alternative<std::exception_ptr>(val_))
    {
        throw std::get<std::exception_ptr>(val_);
    }
}

为了使其工作,我建议你使用std::optional来存储异常指针:

std::variant<std::monostate, std::exception_ptr> val_;

你可以在这里查看使用协程的异常示例:
协程中的异常示例

英文:

You could use the return_void function in your promise_type, something like this:

        void return_void() const
{
if (std::holds_alternative&lt;std::exception_ptr&gt;(val_))
{
throw std::get&lt;std::exception_ptr&gt;(val_);
}
}

In order to work, I recomend you using std::optional to store exception pointer:

std::variant&lt;std::monostate, std::exception_ptr&gt; val_;

You can see an example of exceptions with coroutines here
Exceptions example with coroutines

huangapple
  • 本文由 发表于 2023年6月15日 20:34:30
  • 转载请务必保留本文链接:https://go.coder-hub.com/76482532.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定