You could create a little helper method to handle the logic
public static async Task Foo(Func<Task> method1, Func<Task> method2, int timeoutSecs) {
var taskToCheck = method1();
await Task.Delay(timeoutSecs * 1000);
if (!taskToCheck.IsCompleted)
await method2();
}
Would work, or you could pass the task itself instead of method1 if you prefer. If you need the results of either task you can change the signature, the only problem is that my method should probably bail if method1 finishes early instead of wasting time waiting - I'll be back in 5 with an edit
Ok, It's not the cleanest but this should work. The empty try catches are assuming you will do the error unwrapping elsewhere, these methods just start the Tasks
public static async Task Foo(Func<Task> method1, Func<Task> method2, int timeoutSecs)
{
var taskToCheck = method1();
CancellationTokenSource tokenSource = new CancellationTokenSource();
_ = CancelTokenAfterTaskCompleted(taskToCheck, tokenSource);
try
{
await Task.Delay(timeoutSecs * 1000, tokenSource.Token);
}
catch { }
if (!taskToCheck.IsCompleted)
await method2();
}
private static async Task CancelTokenAfterTaskCompleted(Task taskToCheck, CancellationTokenSource ts) {
try { await taskToCheck; } catch { } finally {
ts.Cancel();
}
}
Another edit, because I'm avoiding work and felt like a nice little challenge. Let noone say I don't know how to overcomplicate things (:<
public static async Task<string> Foo(Dictionary<string, Func<Task<bool>>> connectionMethods, int timeoutSecs, int masterTimeout)
{
CancellationTokenSource completedTokenSource = new CancellationTokenSource();
Dictionary<string, Task<bool>> startedTasks = new Dictionary<string, Task<bool>>();
foreach (var function in connectionMethods)
{
CancellationTokenSource finishedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(completedTokenSource.Token);
var taskToCheck = function.Value();
_ = CancelTokenAfterTaskCompleted(taskToCheck, finishedTokenSource);
_ = CancelTokenAfterTaskReturnedTrue(taskToCheck, completedTokenSource);
startedTasks[function.Key] = taskToCheck;
try
{
await Task.Delay(timeoutSecs * 1000, finishedTokenSource.Token);
}
catch { }
if (completedTokenSource.IsCancellationRequested)
break;
if (!taskToCheck.IsCompleted || taskToCheck.IsFaulted || !(await taskToCheck)) //is the task still running, failed or returned false
continue;
return function.Key;
}
if (!completedTokenSource.IsCancellationRequested)
try
{
await Task.Delay(masterTimeout * 1000, completedTokenSource.Token);
}
catch { }
foreach (var taskSet in startedTasks)
if (taskSet.Value.IsCompleted && !taskSet.Value.IsFaulted && (await taskSet.Value))
return taskSet.Key;
throw new Exception($"None of the methods finished within the alloted timespan");
}
private static async Task CancelTokenAfterTaskCompleted(Task taskToCheck, CancellationTokenSource ts)
{
try { await taskToCheck; }
catch { }
finally
{
ts.Cancel();
}
}
private static async Task CancelTokenAfterTaskReturnedTrue(Task<bool> taskToCheck, CancellationTokenSource ts)
{
try
{
if (await taskToCheck)
ts.Cancel();
}
catch { }
}
this version takes in a dictionary of keys and connection delegates (which return true on successful connection). It then progressively goes through each using the logic you wanted, but at the same time keeps track of whether or not any of the started tasks have succeeded (using a second cancellation token source - I kept two because if you get an error 404 for example, you'd want to move on to the next method immediately, but the main cancellationToken should only end when one succeeds). I've really gotta stop wasting time now and get back to work, hope you enjoy my overcomplicated nightmare of a method.