Skip to content

AwaitableCondition

AwaitableCondition is an abstract base for "wait until X is true" with first-class timeout and cancellation support. You subclass it, implement Evaluate(), wire up whatever events tell you the world has changed, and consumers just await WaitAsync().

Key features

  • Asynchronous waitingasync/await end-to-end, no polling unless you add it yourself
  • Timeout support — configurable; choose whether a timeout throws or returns false
  • Cancellation — cooperative via CancellationToken
  • Extensible hooksOnSuccess / OnFailed / OnCanceled for cleanup
  • No spinEvaluate is only called when you call OnConditionChanged()

Minimal shape

csharp
public Task<bool> WaitAsync();
  • Returns true when Evaluate() returned true.
  • Returns false when the evaluation ran and returned false before timeout (or when the timeout/cancel fired and throwExceptionIfCanceled is false).
  • Throws OperationCanceledException on timeout/cancel when throwExceptionIfCanceled is true.
csharp
var condition = new DatabaseConnectionCondition("Server=localhost;…", timeoutMilliseconds: 30_000);

try
{
    if (await condition.WaitAsync())
    {
        Console.WriteLine("Database is ready!");
    }
}
catch (OperationCanceledException)
{
    Console.WriteLine("Timeout waiting for database connection");
}

Implementation rules

csharp
protected abstract bool Evaluate();
  • Must be implemented by derived classes.
  • Should be fast and non-blocking — it runs on whatever thread signalled OnConditionChanged.
  • Should not throw; return false on failure.

You call OnConditionChanged() from whatever event, poll, or observer signals that the world has changed. The base class then calls Evaluate to decide whether the wait is done.

State hooks

Override to plug into lifecycle events (great for cleanup):

  • OnSuccess — condition was met
  • OnFailed — evaluation returned false
  • OnCanceled — timeout or cancellation fired
csharp
protected override void OnCanceled()
    => logger.LogWarning("Condition wait was canceled after timeout");

Example: file appears

csharp
public class FileCreatedCondition : AwaitableCondition
{
    private readonly string _filePath;
    private readonly FileSystemWatcher _watcher;

    public FileCreatedCondition(string filePath, CancellationToken cancellationToken)
        : base(cancellationToken, throwExceptionIfCanceled: false)
    {
        _filePath = filePath;

        var directory = Path.GetDirectoryName(filePath)!;
        var fileName  = Path.GetFileName(filePath);

        _watcher = new FileSystemWatcher(directory, fileName);
        _watcher.Created += (_, _) => OnConditionChanged();
        _watcher.EnableRaisingEvents = true;
    }

    protected override bool Evaluate()        => File.Exists(_filePath);
    protected override void OnSuccess()       => _watcher.Dispose();
    protected override void OnCanceled()      => _watcher.Dispose();
}

using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(5));
var condition = new FileCreatedCondition(@"C:\temp\ready.flag", cts.Token);
bool ok = await condition.WaitAsync();

Example: HTTP service ready

csharp
public class ServiceReadinessCondition : AwaitableCondition
{
    private readonly HttpClient _http;
    private readonly string _healthUrl;
    private readonly Timer _pollTimer;

    public ServiceReadinessCondition(string healthUrl, TimeSpan pollInterval, int timeoutMs)
        : base(timeoutMs)
    {
        _healthUrl = healthUrl;
        _http = new HttpClient();
        _pollTimer = new Timer(_ => OnConditionChanged(), null, TimeSpan.Zero, pollInterval);
    }

    protected override bool Evaluate()
    {
        try   { return _http.GetAsync(_healthUrl).Result.IsSuccessStatusCode; }
        catch { return false; }
    }

    protected override void OnSuccess()  { _pollTimer.Dispose(); _http.Dispose(); }
    protected override void OnCanceled() { _pollTimer.Dispose(); _http.Dispose(); }
}

API

csharp
public abstract class AwaitableCondition
{
    protected AwaitableCondition(int timeoutMilliseconds);
    protected AwaitableCondition(CancellationToken token, bool throwExceptionIfCanceled = true);

    public Task<bool> WaitAsync();

    protected abstract bool Evaluate();
    protected virtual void OnSuccess();
    protected virtual void OnFailed();
    protected virtual void OnCanceled();
}

Common use cases

  • Service startup — wait for databases, APIs, external services
  • File operations — wait for files to be created or become accessible
  • State synchronisation — wait for application state changes
  • Testing — predictable delays and conditions in integration tests

Servus and happy coding! 🥨🍺