Skip to content

Commit bf4459b

Browse files
authored
Allow resume_agile to be stored in a variable (#1358)
`resume_agile` exposes the ability to save the `await_adapter` in a variable. This was not possible without `resume_agile` because the `await_adapter` had previously been available only via `operator co_await`, which means that it is created only in response to an immediate attempt to `co_await` it, so we knew that it would be consumed before its argument (possibly a temporary) was destructed. `resume_agile` returns the `await_adapter`, and we expect people to await it immediately, but it's possible that they decide to save it in a variable and await it later. In that case, we have to record the `Async` as a value instead of a reference. We forward the `resume_agile` argument into the `Async` so that it moves if given an rvalue reference, or copies if given an lvalue reference. This ensure that the common case where somebody does `co_await resume_agile(DoSomething())`, we do not incur any additional AddRefs or Releases. Now that it's possible to `co_await` the `await_adapter` twice, we have to worry about `await_suspend` being called twice. It had previously assumed that `suspending` was true (since that's how it was constructed), but that is no longer valid in the `resume_agile` case if somebody tries to await the `resume_agile` twice. So we have to force it to `true`. (Now, the second await will fail with "illegal delegate assignment", but our failure to set `suspending` to `true` led to double-resumption, which is super-bad.)
1 parent fac72c8 commit bf4459b

File tree

2 files changed

+44
-5
lines changed

2 files changed

+44
-5
lines changed

strings/base_coroutine_foundation.h

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -151,11 +151,12 @@ namespace winrt::impl
151151

152152
#ifdef WINRT_IMPL_COROUTINES
153153
template <typename Async, bool preserve_context = true>
154-
struct await_adapter : cancellable_awaiter<await_adapter<Async>>
154+
struct await_adapter : cancellable_awaiter<await_adapter<Async, preserve_context>>
155155
{
156-
await_adapter(Async const& async) : async(async) { }
156+
template<typename T>
157+
await_adapter(T&& async) : async(std::forward<T>(async)) { }
157158

158-
Async const& async;
159+
std::conditional_t<preserve_context, Async const&, Async> async;
159160
Windows::Foundation::AsyncStatus status = Windows::Foundation::AsyncStatus::Started;
160161
int32_t failure = 0;
161162
std::atomic<bool> suspending = true;
@@ -190,6 +191,11 @@ namespace winrt::impl
190191
private:
191192
bool register_completed_callback(coroutine_handle<> handle)
192193
{
194+
if constexpr (!preserve_context)
195+
{
196+
// Ensure that the illegal delegate assignment propagates properly.
197+
suspending.store(true, std::memory_order_relaxed);
198+
}
193199
async.Completed(disconnect_aware_handler<preserve_context, await_adapter>(this, handle));
194200
return suspending.exchange(false, std::memory_order_acquire);
195201
}
@@ -257,9 +263,9 @@ namespace winrt::impl
257263
WINRT_EXPORT namespace winrt
258264
{
259265
template<typename Async, typename = std::enable_if_t<std::is_convertible_v<Async, winrt::Windows::Foundation::IAsyncInfo>>>
260-
inline impl::await_adapter<Async, false> resume_agile(Async const& async)
266+
inline impl::await_adapter<std::decay_t<Async>, false> resume_agile(Async&& async)
261267
{
262-
return { async };
268+
return { std::forward<Async>(async) };
263269
};
264270
}
265271

test/test/await_adapter.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,3 +136,36 @@ TEST_CASE("await_adapter_agile")
136136
AgileAsync(dispatcher).get();
137137
controller.ShutdownQueueAsync().get();
138138
}
139+
140+
namespace
141+
{
142+
IAsyncAction AgileAsyncVariable(DispatcherQueue dispatcher)
143+
{
144+
// Switch to the STA.
145+
co_await resume_foreground(dispatcher);
146+
REQUIRE(is_sta());
147+
148+
// Ask for agile resumption of a coroutine that finishes on a background thread.
149+
// Add a 100ms delay to ensure we suspend. Store the resume_agile in a variable
150+
// and await the variable.
151+
auto op = resume_agile(OtherBackgroundDelayAsync());
152+
co_await op;
153+
// We should be on the background thread now.
154+
REQUIRE(!is_sta());
155+
156+
// Second attempt to await the op should fail cleanly.
157+
REQUIRE_THROWS_AS(co_await op, hresult_illegal_delegate_assignment);
158+
// We should still be on the background thread.
159+
REQUIRE(!is_sta());
160+
}
161+
}
162+
163+
164+
TEST_CASE("await_adapter_agile_variable")
165+
{
166+
auto controller = DispatcherQueueController::CreateOnDedicatedThread();
167+
auto dispatcher = controller.DispatcherQueue();
168+
169+
AgileAsyncVariable(dispatcher).get();
170+
controller.ShutdownQueueAsync().get();
171+
}

0 commit comments

Comments
 (0)