С вашим кодом все в порядке, но когда вы читаете его с первого взгляда, не ясно, что он делает. Поэтому я предлагаю вам инкапсулировать эту логику в служебный метод с описательным именем и параметрами:
public static async Task RunSequentially(IEnumerable<Func<Task>> taskFactories,
int timeout = Timeout.Infinite, bool onTimeoutAwaitIncompleteTask = false)
{
using (var cts = new CancellationTokenSource(timeout))
{
if (onTimeoutAwaitIncompleteTask)
{
await Task.Run(async () =>
{
foreach (var taskFactory in taskFactories)
{
if (cts.IsCancellationRequested) throw new TimeoutException();
await taskFactory();
}
});
}
else // On timeout return immediately
{
var allSequentially = Task.Run(async () =>
{
foreach (var taskFactory in taskFactories)
{
cts.Token.ThrowIfCancellationRequested();
var task = taskFactory(); // Synchronous part of task
cts.Token.ThrowIfCancellationRequested();
await task; // Asynchronous part of task
}
}, cts.Token);
var timeoutTask = new Task(() => {}, cts.Token);
var completedTask = await Task.WhenAny(allSequentially, timeoutTask);
if (completedTask.IsCanceled) throw new TimeoutException();
await completedTask; // Propagate any exception
}
}
}
Этот код отличается от вашего в том, что он выдает TimeoutException
по истечении времени ожидания. Я думаю, что лучше заставить вызывающую сторону явно обработать это исключение, а не скрывать тот факт, что время ожидания операции истекло. Вызывающая сторона может игнорировать исключение, оставив пустой блок catch:
try
{
await RunSequentially(new[] { t1, t2, t3, t4 },
timeout: 4000,
onTimeoutAwaitIncompleteTask: true);
}
catch (TimeoutException)
{
// Do nothing
}