This looks mostly good to me but I think there's still some things that can be improved:
* Cancel objects seems to be only useful for client-originated cancels, we could keep using the cancelled_asyncs list for server-cancelled asyncs. This avoids unnecessary cancel object creation, and the sync creation could be made greedy instead.
* The ownership of the asyncs still is a bit unclear. They were only weakly owned by their process (in asyncs or cancelled_asyncs lists), and removed from these lists when the async is last released. So I don't think they should be owned by the cancel object either, especially as the same list entry is used. The cancel objects can be owned by their process (or not, if we want to rely solely on the handle reference, not sure it makes much difference).
I have implemented these two points in https://gitlab.winehq.org/rbernon/wine/-/commits/wip/7797, please have a look and update the MR with the changes if they look good to you.
There's another thing which I'm unsure about: The cancel object signaling is only triggered in `async_set_result`, it's unclear to me whether this is guaranteed to always be called in an async lifetime, after it has been cancelled and before it is last released. We were waiting on every async to be signaled before, it seems to me that this can happen through different call paths as well.
As I'm suggesting to keep the async weakly referenced by their cancel object, I think we can also call `async_complete_cancel` in `async_destroy` just in case (and I did that in the branch above). I'm then wondering whether it is then still necessary to call it in `async_set_result`?