@@ -10,12 +10,12 @@ namespace Bunit.Extensions.WaitForHelpers;
10
10
/// </summary>
11
11
public abstract class WaitForHelper < T > : IDisposable
12
12
{
13
- private readonly Timer timer ;
14
13
private readonly TaskCompletionSource < T > checkPassedCompletionSource ;
15
14
private readonly Func < ( bool CheckPassed , T Content ) > completeChecker ;
16
15
private readonly IRenderedFragmentBase renderedFragment ;
17
16
private readonly ILogger < WaitForHelper < T > > logger ;
18
17
private readonly TestRenderer renderer ;
18
+ private readonly Timer ? timer ;
19
19
private bool isDisposed ;
20
20
private int checkCount ;
21
21
private Exception ? capturedException ;
@@ -60,25 +60,37 @@ protected WaitForHelper(
60
60
. Renderer ;
61
61
checkPassedCompletionSource = new TaskCompletionSource < T > ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
62
62
63
+ // Create the wait task and run the initial check
64
+ // and subscribe to the OnAfterRender event.
65
+ // This must happen before the timer is started,
66
+ // as the check happens inside the renderers synchronization context,
67
+ // and that may be blocked longer than the timeout on overloaded systems,
68
+ // resulting in the timer completing before a single check has
69
+ // has a chance to complete.
63
70
WaitTask = CreateWaitTask ( ) ;
64
- timer = new Timer (
65
- static ( state ) =>
66
- {
67
- var @this = ( WaitForHelper < T > ) state ! ;
68
- @this . logger . LogWaiterTimedOut ( @this . renderedFragment . ComponentId ) ;
69
- @this . checkPassedCompletionSource . TrySetException (
70
- new WaitForFailedException (
71
- @this . TimeoutErrorMessage ?? string . Empty ,
72
- @this . checkCount ,
73
- @this . renderedFragment . RenderCount ,
74
- @this . renderer . RenderCount ,
75
- @this . capturedException ) ) ;
76
- } ,
77
- this ,
78
- GetRuntimeTimeout ( timeout ) ,
79
- Timeout . InfiniteTimeSpan ) ;
80
-
81
- InitializeWaiting ( ) ;
71
+ CheckAndInitializeWaiting ( ) ;
72
+
73
+ // If the initial check did not complete successfully,
74
+ // start the timer and recheck after every render until the timer expires.
75
+ if ( ! WaitTask . IsCompleted )
76
+ {
77
+ timer = new Timer (
78
+ static ( state ) =>
79
+ {
80
+ var @this = ( WaitForHelper < T > ) state ! ;
81
+ @this . logger . LogWaiterTimedOut ( @this . renderedFragment . ComponentId ) ;
82
+ @this . checkPassedCompletionSource . TrySetException (
83
+ new WaitForFailedException (
84
+ @this . TimeoutErrorMessage ?? string . Empty ,
85
+ @this . checkCount ,
86
+ @this . renderedFragment . RenderCount ,
87
+ @this . renderer . RenderCount ,
88
+ @this . capturedException ) ) ;
89
+ } ,
90
+ this ,
91
+ GetRuntimeTimeout ( timeout ) ,
92
+ Timeout . InfiniteTimeSpan ) ;
93
+ }
82
94
}
83
95
84
96
/// <summary>
@@ -105,13 +117,13 @@ protected virtual void Dispose(bool disposing)
105
117
return ;
106
118
107
119
isDisposed = true ;
108
- timer . Dispose ( ) ;
120
+ timer ? . Dispose ( ) ;
109
121
checkPassedCompletionSource . TrySetCanceled ( ) ;
110
122
renderedFragment . OnAfterRender -= OnAfterRender ;
111
123
logger . LogWaiterDisposed ( renderedFragment . ComponentId ) ;
112
124
}
113
125
114
- private void InitializeWaiting ( )
126
+ private void CheckAndInitializeWaiting ( )
115
127
{
116
128
if ( ! WaitTask . IsCompleted )
117
129
{
0 commit comments