@@ -18,13 +18,9 @@ namespace
18
18
19
19
static handle signal{ CreateEventW (nullptr , false , false , nullptr ) };
20
20
21
- IAsyncAction OtherForegroundAsync ()
21
+ IAsyncAction OtherForegroundAsync (DispatcherQueue dispatcher )
22
22
{
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.
28
24
co_await resume_foreground (dispatcher);
29
25
}
30
26
@@ -35,37 +31,37 @@ namespace
35
31
co_await resume_background ();
36
32
}
37
33
38
- IAsyncAction ForegroundAsync (DispatcherQueue dispatcher)
34
+ // Coroutine that completes on dispatcher1, while potentially blocking dispatcher2.
35
+ IAsyncAction ForegroundAsync (DispatcherQueue dispatcher1, DispatcherQueue dispatcher2)
39
36
{
40
37
REQUIRE (!is_sta ());
41
- co_await resume_foreground (dispatcher );
38
+ co_await resume_foreground (dispatcher1 );
42
39
REQUIRE (is_sta ());
43
40
44
41
// This exercises one STA thread waiting on another thus one context callback
45
42
// completing on another.
46
43
uint32_t id = GetCurrentThreadId ();
47
- co_await OtherForegroundAsync ();
44
+ co_await OtherForegroundAsync (dispatcher2 );
48
45
REQUIRE (id == GetCurrentThreadId ());
49
46
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.
54
49
Sleep (100 );
55
50
}
56
51
57
- fire_and_forget SignalFromForeground (DispatcherQueue dispatcher )
52
+ fire_and_forget SignalFromForeground (DispatcherQueue dispatcher1 )
58
53
{
59
54
REQUIRE (!is_sta ());
60
- co_await resume_foreground (dispatcher );
55
+ co_await resume_foreground (dispatcher1 );
61
56
REQUIRE (is_sta ());
62
57
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.
65
61
REQUIRE (SetEvent (signal.get ()));
66
62
}
67
63
68
- IAsyncAction BackgroundAsync (DispatcherQueue dispatcher )
64
+ IAsyncAction BackgroundAsync (DispatcherQueue dispatcher1, DispatcherQueue dispatcher2 )
69
65
{
70
66
// Switch to a background (MTA) thread.
71
67
co_await resume_background ();
@@ -76,19 +72,19 @@ namespace
76
72
co_await OtherBackgroundAsync ();
77
73
REQUIRE (!is_sta ());
78
74
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 );
81
77
82
78
// 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).
84
80
REQUIRE (!is_sta ());
85
81
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 );
89
85
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 .
92
88
REQUIRE (WAIT_OBJECT_0 == WaitForSingleObject (signal.get (), INFINITE));
93
89
}
94
90
}
@@ -99,9 +95,44 @@ TEST_CASE("await_adapter", "[.clang-crash]")
99
95
#else
100
96
TEST_CASE (" await_adapter" )
101
97
#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" )
102
132
{
103
133
auto controller = DispatcherQueueController::CreateOnDedicatedThread ();
104
134
auto dispatcher = controller.DispatcherQueue ();
105
135
106
- BackgroundAsync (dispatcher).get ();
136
+ AgileAsync (dispatcher).get ();
137
+ controller.ShutdownQueueAsync ().get ();
107
138
}
0 commit comments