Skip to content

Instantly share code, notes, and snippets.

@Aetopia
Created June 22, 2026 12:38
Show Gist options
  • Select an option

  • Save Aetopia/02249d7bca42cfb1346326e33a30d947 to your computer and use it in GitHub Desktop.

Select an option

Save Aetopia/02249d7bca42cfb1346326e33a30d947 to your computer and use it in GitHub Desktop.
Asynchronous Object Probing with automatic disposal of non-results.

The following snippet probes the specified input and returns the first hit as it. It will then automatically disposable of non-results for cleanup.

This snippet was made to experiment how this system could be implemented with System.Net.Http & friends.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using static System.Threading.Tasks.TaskContinuationOptions;
static class Program
{
static async Task Main()
{
int[] timeouts = [.. Enumerable.Range(1, 9).Select(static _ => _ * 1)];
var disposable = await DisposableService.ProbeAsync(timeouts);
Console.WriteLine($"{disposable?.ToString() ?? "?"} : Result\n");
Console.ReadKey();
}
}
sealed class Disposable(int timeout) : IDisposable
{
public override string ToString()
{
return $"{timeout}";
}
public void Dispose()
{
Console.WriteLine($"{timeout} : Disposed\n");
}
internal static async Task<Disposable> GetAsync(int timeout, CancellationToken token)
{
await Task.Delay(timeout, token);
if (timeout == 1)
throw new ExternalException();
return new Disposable(timeout);
}
}
static class DisposableService
{
internal static async Task<Disposable?> ProbeAsync(IReadOnlyList<int> timeouts)
{
using CancellationTokenSource cts = new();
var tasks = Probe(timeouts, cts.Token);
try
{
await foreach (var task in Task.WhenEach(tasks))
{
var value = await task;
if (value is null) continue;
cts.Cancel();
tasks.Remove(task);
return value;
}
return null;
}
finally
{
foreach (var task in tasks) _ = task.ContinueWith(static async task =>
{
var value = await task;
Console.WriteLine($"{value?.ToString() ?? "?"} : {(value is null ? "Null" : "Object")}\n");
value?.Dispose();
}, RunContinuationsAsynchronously);
}
}
static List<Task<Disposable?>> Probe(IReadOnlyList<int> timeouts, CancellationToken token)
{
List<Task<Disposable?>> tasks = new(timeouts.Count);
foreach (var timeout in timeouts)
tasks.Add(ProbeAsync(timeout, token));
return tasks;
}
static async Task<Disposable?> ProbeAsync(int timeout, CancellationToken token)
{
Console.WriteLine($"{timeout} : Start\n");
try
{
var disposable = await Disposable.GetAsync(timeout, token);
Console.WriteLine($"{timeout} : Created\n");
return disposable;
}
catch
{
return null;
}
finally
{
Console.WriteLine($"{timeout} : {(token.IsCancellationRequested ? "Cancelled" : "Running")}\n");
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment