@@ -31,6 +31,8 @@ static readonly ReadOnlyDictionary<string, object> _emptyParametersDictionary
31
31
32
32
private CancellationTokenSource _onNavigateCts ;
33
33
34
+ private Task _previousOnNavigateTask = Task . CompletedTask ;
35
+
34
36
private readonly HashSet < Assembly > _assemblies = new HashSet < Assembly > ( ) ;
35
37
36
38
private bool _onNavigateCalled = false ;
@@ -112,7 +114,8 @@ public async Task SetParametersAsync(ParameterView parameters)
112
114
if ( ! _onNavigateCalled )
113
115
{
114
116
_onNavigateCalled = true ;
115
- await RunOnNavigateAsync ( NavigationManager . ToBaseRelativePath ( _locationAbsolute ) ) ;
117
+ await RunOnNavigateWithRefreshAsync ( NavigationManager . ToBaseRelativePath ( _locationAbsolute ) , isNavigationIntercepted : false ) ;
118
+ return ;
116
119
}
117
120
118
121
Refresh ( isNavigationIntercepted : false ) ;
@@ -122,7 +125,6 @@ public async Task SetParametersAsync(ParameterView parameters)
122
125
public void Dispose ( )
123
126
{
124
127
NavigationManager . LocationChanged -= OnLocationChanged ;
125
- _onNavigateCts ? . Dispose ( ) ;
126
128
}
127
129
128
130
private static string StringUntilAny ( string str , char [ ] chars )
@@ -147,7 +149,7 @@ private void RefreshRouteTable()
147
149
148
150
}
149
151
150
- private void Refresh ( bool isNavigationIntercepted )
152
+ internal virtual void Refresh ( bool isNavigationIntercepted )
151
153
{
152
154
RefreshRouteTable ( ) ;
153
155
@@ -190,28 +192,31 @@ private void Refresh(bool isNavigationIntercepted)
190
192
}
191
193
}
192
194
193
- private async Task RunOnNavigateAsync ( string path )
195
+ private async ValueTask < bool > RunOnNavigateAsync ( string path , Task previousOnNavigate )
194
196
{
195
197
// If this router instance does not provide an OnNavigateAsync parameter
196
198
// then we render the component associated with the route as per usual.
197
199
if ( ! OnNavigateAsync . HasDelegate )
198
200
{
199
- return ;
201
+ return true ;
200
202
}
201
203
202
204
// If we've already invoked a task and stored its CTS, then
203
- // cancel the existing task.
204
- _onNavigateCts ? . Dispose ( ) ;
205
+ // cancel that existing CTS.
206
+ _onNavigateCts ? . Cancel ( ) ;
207
+ // Then make sure that the task has been completed cancelled or
208
+ // completed before continuing with the execution of this current task.
209
+ await previousOnNavigate ;
205
210
206
211
// Create a new cancellation token source for this instance
207
212
_onNavigateCts = new CancellationTokenSource ( ) ;
208
213
var navigateContext = new NavigationContext ( path , _onNavigateCts . Token ) ;
209
214
210
215
// Create a cancellation task based on the cancellation token
211
216
// associated with the current running task.
212
- var cancellationTaskSource = new TaskCompletionSource ( ) ;
217
+ var cancellationTcs = new TaskCompletionSource ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
213
218
navigateContext . CancellationToken . Register ( state =>
214
- ( ( TaskCompletionSource ) state ) . SetResult ( ) , cancellationTaskSource ) ;
219
+ ( ( TaskCompletionSource ) state ) . SetResult ( ) , cancellationTcs ) ;
215
220
216
221
var task = OnNavigateAsync . InvokeAsync ( navigateContext ) ;
217
222
@@ -221,13 +226,34 @@ private async Task RunOnNavigateAsync(string path)
221
226
_renderHandle . Render ( Navigating ) ;
222
227
}
223
228
224
- await Task . WhenAny ( task , cancellationTaskSource . Task ) ;
229
+ var completedTask = await Task . WhenAny ( task , cancellationTcs . Task ) ;
230
+ return task == completedTask ;
225
231
}
226
232
227
- private async Task RunOnNavigateWithRefreshAsync ( string path , bool isNavigationIntercepted )
233
+ internal async Task RunOnNavigateWithRefreshAsync ( string path , bool isNavigationIntercepted )
228
234
{
229
- await RunOnNavigateAsync ( path ) ;
230
- Refresh ( isNavigationIntercepted ) ;
235
+ // We cache the Task representing the previously invoked RunOnNavigateWithRefreshAsync
236
+ // that is stored
237
+ var previousTask = _previousOnNavigateTask ;
238
+ // Then we create a new one that represents our current invocation and store it
239
+ // globally for the next invocation. Note to the developer, if the WASM runtime
240
+ // support multi-threading then we'll need to implement the appropriate locks
241
+ // here to ensure that the cached previous task is overwritten incorrectly.
242
+ var tcs = new TaskCompletionSource ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
243
+ _previousOnNavigateTask = tcs . Task ;
244
+ try
245
+ {
246
+ // And pass an indicator for the previous task to the currently running one.
247
+ var shouldRefresh = await RunOnNavigateAsync ( path , previousTask ) ;
248
+ if ( shouldRefresh )
249
+ {
250
+ Refresh ( isNavigationIntercepted ) ;
251
+ }
252
+ }
253
+ finally
254
+ {
255
+ tcs . SetResult ( ) ;
256
+ }
231
257
}
232
258
233
259
private void OnLocationChanged ( object sender , LocationChangedEventArgs args )
0 commit comments