@@ -20,9 +20,9 @@ internal class WebAssemblyRenderer : Renderer
20
20
{
21
21
private readonly ILogger _logger ;
22
22
private readonly int _webAssemblyRendererId ;
23
+ private readonly QueueWithLast < IncomingEventInfo > deferredIncomingEvents = new ( ) ;
23
24
24
25
private bool isDispatchingEvent ;
25
- private Queue < IncomingEventInfo > deferredIncomingEvents = new Queue < IncomingEventInfo > ( ) ;
26
26
27
27
/// <summary>
28
28
/// Constructs an instance of <see cref="WebAssemblyRenderer"/>.
@@ -103,7 +103,23 @@ protected override Task UpdateDisplayAsync(in RenderBatch batch)
103
103
_webAssemblyRendererId ,
104
104
batch ) ;
105
105
106
- return Task . CompletedTask ;
106
+ if ( deferredIncomingEvents . Count == 0 )
107
+ {
108
+ // In the vast majority of cases, since the call to update the UI is synchronous,
109
+ // we just return a pre-completed task from here.
110
+ return Task . CompletedTask ;
111
+ }
112
+ else
113
+ {
114
+ // However, in the rare case where JS sent us any event notifications that we had to
115
+ // defer until later, we behave as if the renderbatch isn't acknowledged until we have at
116
+ // least dispatched those event calls. This is to make the WebAssembly behavior more
117
+ // consistent with the Server behavior, which receives batch acknowledgements asynchronously
118
+ // and they are queued up with any other calls from JS such as event calls. If we didn't
119
+ // do this, then the order of execution could be inconsistent with Server, and in fact
120
+ // leads to a specific bug: https://github.com/dotnet/aspnetcore/issues/26838
121
+ return deferredIncomingEvents . Last . StartHandlerCompletionSource . Task ;
122
+ }
107
123
}
108
124
109
125
/// <inheritdoc />
@@ -144,7 +160,7 @@ public override Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo? ev
144
160
{
145
161
var info = new IncomingEventInfo ( eventHandlerId , eventFieldInfo , eventArgs ) ;
146
162
deferredIncomingEvents . Enqueue ( info ) ;
147
- return info . TaskCompletionSource . Task ;
163
+ return info . FinishHandlerCompletionSource . Task ;
148
164
}
149
165
else
150
166
{
@@ -171,16 +187,20 @@ public override Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo? ev
171
187
private async Task ProcessNextDeferredEventAsync ( )
172
188
{
173
189
var info = deferredIncomingEvents . Dequeue ( ) ;
174
- var taskCompletionSource = info . TaskCompletionSource ;
175
190
176
191
try
177
192
{
178
- await DispatchEventAsync ( info . EventHandlerId , info . EventFieldInfo , info . EventArgs ) ;
179
- taskCompletionSource . SetResult ( ) ;
193
+ var handlerTask = DispatchEventAsync ( info . EventHandlerId , info . EventFieldInfo , info . EventArgs ) ;
194
+ info . StartHandlerCompletionSource . SetResult ( ) ;
195
+ await handlerTask ;
196
+ info . FinishHandlerCompletionSource . SetResult ( ) ;
180
197
}
181
198
catch ( Exception ex )
182
199
{
183
- taskCompletionSource . SetException ( ex ) ;
200
+ // Even if the handler threw synchronously, we at least started processing, so always complete successfully
201
+ info . StartHandlerCompletionSource . TrySetResult ( ) ;
202
+
203
+ info . FinishHandlerCompletionSource . SetException ( ex ) ;
184
204
}
185
205
}
186
206
@@ -189,14 +209,16 @@ readonly struct IncomingEventInfo
189
209
public readonly ulong EventHandlerId ;
190
210
public readonly EventFieldInfo ? EventFieldInfo ;
191
211
public readonly EventArgs EventArgs ;
192
- public readonly TaskCompletionSource TaskCompletionSource ;
212
+ public readonly TaskCompletionSource StartHandlerCompletionSource ;
213
+ public readonly TaskCompletionSource FinishHandlerCompletionSource ;
193
214
194
215
public IncomingEventInfo ( ulong eventHandlerId , EventFieldInfo ? eventFieldInfo , EventArgs eventArgs )
195
216
{
196
217
EventHandlerId = eventHandlerId ;
197
218
EventFieldInfo = eventFieldInfo ;
198
219
EventArgs = eventArgs ;
199
- TaskCompletionSource = new TaskCompletionSource ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
220
+ StartHandlerCompletionSource = new TaskCompletionSource ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
221
+ FinishHandlerCompletionSource = new TaskCompletionSource ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
200
222
}
201
223
}
202
224
@@ -225,5 +247,30 @@ public static void UnhandledExceptionRenderingComponent(ILogger logger, Exceptio
225
247
exception ) ;
226
248
}
227
249
}
250
+
251
+ private class QueueWithLast < T >
252
+ {
253
+ private readonly Queue < T > _items = new ( ) ;
254
+
255
+ public int Count => _items . Count ;
256
+
257
+ public T ? Last { get ; private set ; }
258
+
259
+ public T Dequeue ( )
260
+ {
261
+ if ( _items . Count == 1 )
262
+ {
263
+ Last = default ;
264
+ }
265
+
266
+ return _items . Dequeue ( ) ;
267
+ }
268
+
269
+ public void Enqueue ( T item )
270
+ {
271
+ Last = item ;
272
+ _items . Enqueue ( item ) ;
273
+ }
274
+ }
228
275
}
229
276
}
0 commit comments