Skip to content

Conversation

@oldnewthing
Copy link
Member

@oldnewthing oldnewthing commented Jun 27, 2020

If the Completed handler runs synchronously, that would cause us to destroy the operation while a method on the operation is still running. That's not very nice.

This stems from the fact that synchronous completion causes the coroutine to resume before put_Completed returns. Resuming the coroutine causes it to get the result and then destroy the async operation. If the Completed handler is called synchronously from within put_Completed (which will happen if the operation has already completed), the operation will be destroyed while the put_Completed call is still outstanding. To avoid this problem, await_suspend extends the lifetime of the async operation across the call to put_Completed, so that the synchronous completion doesn't destroy the async operation prematurely.

If the operation completes asynchronously, then the destruction happens at coroutine resumption, which is fine. The put_Completed method returned a long time ago.

We make a copy of the async operation to ensure it is not destroyed while put_Completed is running. This costs us an AddRef/Release pair, but avoiding the AddRef/Release will cost us a few atomic operations (some of which require memory barriers), so it's basically a wash.

A more advanced solution would steal the async operation into await_suspend, and then use the IAsyncAction/IAsyncOperation passed to the completion handler to get the results. We'll use the simple solution for now.

If the Completed handler runs synchronously, that would
cause us to destroy the operation while a method on
the operation is still running. That's not very nice.

This stems from the fact that synchronous completion
causes the coroutine to resume before put_Completed
returns, and resuming the coroutine causes it to get
the result and then destroy the async operation.
To avoid this problem, we extend the lifetime of the
async operation inside await_suspend, so that the
synchronous completion doesn't destruct the async operation
until after put_Completed has returned.

If the operation completes asynchronously, then the
destruction happens at coroutine resumption, which is fine.
The put_Completed method returned a long time ago.

We cannot "steal" the async operation into await_suspend,
because await_resume needs the operation to get the results.
We have to make a copy. This costs us an AddRef/Release pair,
but avoiding the AddRef/Release will cost us a few atomic
operations (and memory barriers), so it's basically a wash.
Copy link
Collaborator

@kennykerr kennykerr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@kennykerr
Copy link
Collaborator

/azp run

@azure-pipelines
Copy link

No pipelines are associated with this pull request.

@kennykerr kennykerr merged commit f67e55a into microsoft:master Jun 28, 2020
@oldnewthing oldnewthing deleted the sync-completion branch July 8, 2020 16:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants