Created
October 1, 2023 07:52
-
-
Save olivier-spinelli/c075b672c26a3ba26801e55be3d3e5d9 to your computer and use it in GitHub Desktop.
A "breakpoint" that let other threads run.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Implemented as an extension method (on IMonitorTestHelper that provides logging) but this | |
// dependency can easily ve removed. | |
public static class MonitorTestHelperExtension | |
{ | |
sealed class Resumer | |
{ | |
internal readonly TaskCompletionSource _tcs; | |
readonly Timer _timer; | |
readonly Func<bool, bool> _resume; | |
bool _reentrant; | |
internal Resumer( Func<bool, bool> resume ) | |
{ | |
_timer = new Timer( OnTimer, null, 1000, 1000 ); | |
_tcs = new TaskCompletionSource( TaskCreationOptions.RunContinuationsAsynchronously ); | |
_resume = resume; | |
} | |
void OnTimer( object? _ ) | |
{ | |
if( _reentrant ) return; | |
_reentrant = true; | |
if( _resume( false ) ) | |
{ | |
_tcs.SetResult(); | |
_timer.Dispose(); | |
} | |
_reentrant = false; | |
} | |
} | |
/// <summary> | |
/// Asynchronously blocks until true is returned from the callback (the callback is called every second). | |
/// This can be used only when <see cref="Debugger.IsAttached"/> is true: this is ignored otherwise. | |
/// <para> | |
/// This is intended to let context alive for an undetermined delay, this can be seen as an interruptible | |
/// <c>await Task.Delay( Timeout.Infinite );</c> or a breakpoint that suspends the current task but let | |
/// all the other tasks and threads run. | |
/// </para> | |
/// <para> | |
/// Usage: set a breakpoint in the callback and set the resume variable to true (typically via the watch window) | |
/// to continue the execution. | |
/// <code> | |
/// Put a breakpoint here | |
/// | | |
/// await TestHelper.SuspendAsync( resume => resume ); | |
/// </code> | |
/// </para> | |
/// </summary> | |
/// <param name="resume">callback always called with false that completes the returned task when true is returned.</param> | |
/// <returns>The task to await.</returns> | |
public static Task SuspendAsync( this Testing.IMonitorTestHelper @this, | |
Func<bool, bool> resume, | |
[CallerMemberName] string? testName = null, | |
[CallerLineNumber] int lineNumber = 0, | |
[CallerFilePath] string? fileName = null ) | |
{ | |
Throw.CheckNotNullArgument( resume ); | |
if( !Debugger.IsAttached ) | |
{ | |
@this.Monitor.Warn( $"TestHelper.SuspendAsync called from '{testName}' method while no debugger is attached. Ignoring it.", lineNumber, fileName ); | |
return Task.CompletedTask; | |
} | |
@this.Monitor.Info( $"TestHelper.SuspendAsync called from '{testName}' method.", lineNumber, fileName ); | |
return new Resumer( resume )._tcs.Task; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment