@@ -18,13 +18,9 @@ namespace
1818
1919 static handle signal{ CreateEventW (nullptr , false , false , nullptr ) };
2020
21- IAsyncAction OtherForegroundAsync ()
21+ IAsyncAction OtherForegroundAsync (DispatcherQueue dispatcher )
2222 {
23- // Simple coroutine that completes on a unique STA thread.
24-
25- auto controller = DispatcherQueueController::CreateOnDedicatedThread ();
26- auto dispatcher = controller.DispatcherQueue ();
27-
23+ // Simple coroutine that completes on the specified STA thread.
2824 co_await resume_foreground (dispatcher);
2925 }
3026
@@ -35,37 +31,37 @@ namespace
3531 co_await resume_background ();
3632 }
3733
38- IAsyncAction ForegroundAsync (DispatcherQueue dispatcher)
34+ // Coroutine that completes on dispatcher1, while potentially blocking dispatcher2.
35+ IAsyncAction ForegroundAsync (DispatcherQueue dispatcher1, DispatcherQueue dispatcher2)
3936 {
4037 REQUIRE (!is_sta ());
41- co_await resume_foreground (dispatcher );
38+ co_await resume_foreground (dispatcher1 );
4239 REQUIRE (is_sta ());
4340
4441 // This exercises one STA thread waiting on another thus one context callback
4542 // completing on another.
4643 uint32_t id = GetCurrentThreadId ();
47- co_await OtherForegroundAsync ();
44+ co_await OtherForegroundAsync (dispatcher2 );
4845 REQUIRE (id == GetCurrentThreadId ());
4946
50- // This just avoids the ForegroundAsync coroutine completing before
51- // BackgroundAsync waits on the result, forcing the Completed handler
52- // to be called on the foreground thread. This just makes the test
53- // success/failure more predictable.
47+ // This Sleep() makes it more likely that the caller will actually suspend in await_suspend,
48+ // so that the Completed handler triggers a resumption from the dispatcher1 thread.
5449 Sleep (100 );
5550 }
5651
57- fire_and_forget SignalFromForeground (DispatcherQueue dispatcher )
52+ fire_and_forget SignalFromForeground (DispatcherQueue dispatcher1 )
5853 {
5954 REQUIRE (!is_sta ());
60- co_await resume_foreground (dispatcher );
55+ co_await resume_foreground (dispatcher1 );
6156 REQUIRE (is_sta ());
6257
63- // Previously, this signal was never raised because the foreground thread
64- // was always blocked waiting for ContextCallback to return.
58+ // Previously, we never got here because of a deadlock:
59+ // The dispatcher1 thread was blocked waiting for ContextCallback to return,
60+ // but the ContextCallback is waiting for this event to get signaled.
6561 REQUIRE (SetEvent (signal.get ()));
6662 }
6763
68- IAsyncAction BackgroundAsync (DispatcherQueue dispatcher )
64+ IAsyncAction BackgroundAsync (DispatcherQueue dispatcher1, DispatcherQueue dispatcher2 )
6965 {
7066 // Switch to a background (MTA) thread.
7167 co_await resume_background ();
@@ -76,19 +72,19 @@ namespace
7672 co_await OtherBackgroundAsync ();
7773 REQUIRE (!is_sta ());
7874
79- // Wait for a coroutine that completes on a foreground (STA) thread .
80- co_await ForegroundAsync (dispatcher );
75+ // Wait for a coroutine that completes on a the dispatcher1 thread (STA).
76+ co_await ForegroundAsync (dispatcher1, dispatcher2 );
8177
8278 // Resumption should automatically switch to a background (MTA) thread
83- // without blocking the Completed handler (which would in turn block the foreground thread).
79+ // without blocking the Completed handler (which would in turn block the dispatcher1 thread).
8480 REQUIRE (!is_sta ());
8581
86- // Attempt to signal from the foreground thread under the assumption
87- // that the foreground thread is not blocked.
88- SignalFromForeground (dispatcher );
82+ // Attempt to signal from the dispatcher1 thread under the assumption
83+ // that the dispatcher1 thread is not blocked.
84+ SignalFromForeground (dispatcher1 );
8985
90- // Block the background (MTA) thread indefinitely until the signal is raied .
91- // Previously this would deadlock .
86+ // Block the background (MTA) thread indefinitely until the signal is raised .
87+ // Previously this would hang because the signal never got raised .
9288 REQUIRE (WAIT_OBJECT_0 == WaitForSingleObject (signal.get (), INFINITE));
9389 }
9490}
@@ -99,9 +95,44 @@ TEST_CASE("await_adapter", "[.clang-crash]")
9995#else
10096TEST_CASE (" await_adapter" )
10197#endif
98+ {
99+ auto controller1 = DispatcherQueueController::CreateOnDedicatedThread ();
100+ auto controller2 = DispatcherQueueController::CreateOnDedicatedThread ();
101+
102+ BackgroundAsync (controller1.DispatcherQueue (), controller2.DispatcherQueue ()).get ();
103+ controller1.ShutdownQueueAsync ().get ();
104+ controller2.ShutdownQueueAsync ().get ();
105+ }
106+
107+ namespace
108+ {
109+ IAsyncAction OtherBackgroundDelayAsync ()
110+ {
111+ // Simple coroutine that completes on some MTA thread after a brief delay
112+ // to ensure that the caller has suspended.
113+
114+ co_await resume_after (100ms);
115+ }
116+
117+ IAsyncAction AgileAsync (DispatcherQueue dispatcher)
118+ {
119+ // Switch to the STA.
120+ co_await resume_foreground (dispatcher);
121+ REQUIRE (is_sta ());
122+
123+ // Ask for agile resumption of a coroutine that finishes on a background thread.
124+ // Add a 100ms delay to ensure we suspend.
125+ co_await resume_agile (OtherBackgroundDelayAsync ());
126+ // We should be on the background thread now.
127+ REQUIRE (!is_sta ());
128+ }
129+ }
130+
131+ TEST_CASE (" await_adapter_agile" )
102132{
103133 auto controller = DispatcherQueueController::CreateOnDedicatedThread ();
104134 auto dispatcher = controller.DispatcherQueue ();
105135
106- BackgroundAsync (dispatcher).get ();
136+ AgileAsync (dispatcher).get ();
137+ controller.ShutdownQueueAsync ().get ();
107138}
0 commit comments