4
\$\begingroup\$

I've created a method which allows to run tasks where results might not be fully computed but still are considered valid. ExecuteFor method can be used if you have limited amount of time in which you would like to calculate something, and even if calculation is not completed, you would want still to have the results.

internal class Program
{
    /// <summary>
    /// Executes defined task within a spiecified time limits and returns a result
    /// of that exection. Result might not be completed fully.
    /// </summary>
    /// <typeparam name="T">Return type of a task</typeparam>
    /// <param name="timeSpan">Time span after which timeout occurs.</param>
    /// <param name="task">Task to execute within limits of time span.</param>
    /// <returns>Result of a given task. Result can be partial.</returns>
    private static async Task<T> ExecuteFor<T>(
        TimeSpan timeSpan,
        Func<CancellationTokenSource, Task<T>> task)
    {
        var cts = new CancellationTokenSource();
        var token = cts.Token;

        Task<T> resultTask = Task.Run(() => task(cts));
        if (resultTask.Wait(timeSpan) == false)
        {
            cts.Cancel();
        }

        return await resultTask;
    }
    
    private static async Task<IEnumerable<int>> GenerateNumbers(CancellationTokenSource cts)
    {
        List<int> numbers = [];

        while (cts.IsCancellationRequested == false)
        {
            // Generate numbers until one second has passed
            await Task.Delay(Random.Shared.Next(70, 150));
            numbers.Add(Random.Shared.Next());
        }

        return numbers;
    }

    public static async Task Main(string[] args)
    {
        TimeSpan shortTimeSpan = TimeSpan.FromSeconds(2);
        TimeSpan longerTimeSpan = TimeSpan.FromSeconds(5);

        IEnumerable<int> result = await ExecuteFor(
            shortTimeSpan,
            GenerateNumbers);

        PrintResultIfAny(shortTimeSpan.Seconds, result);

        result = await ExecuteFor(
            longerTimeSpan,
            GenerateNumbers);

        PrintResultIfAny(longerTimeSpan.Seconds, result);
    }

    private static void PrintResultIfAny(int timeSpanInSeconds, IEnumerable<int> result)
    {
        // Print the result
        if (result is not null)
        {
            Console.WriteLine($"Numbers generated within time span " +
                $"of {timeSpanInSeconds} seconds:");

            foreach (int num in result)
            {
                Console.WriteLine(num);
            }
        }
        else
        {
            Console.WriteLine($"Execution canceled after {timeSpanInSeconds} seconds.");
        }
    }
}
\$\endgroup\$
2
  • \$\begingroup\$ Is there any particular reason why don't you use Polly for this? \$\endgroup\$ Apr 19 at 7:10
  • \$\begingroup\$ Have you tried Task.ContinueWith ? \$\endgroup\$
    – iSR5
    Apr 19 at 7:48

1 Answer 1

4
\$\begingroup\$

I did a little bit of cleanup. The CancellationTokenSource can take a TimeSpan, which you have in hand. Note also that it is an IDisposable resource and therefore should be in a using. Also, the target methods should only need a CancellationToken to check. Finally, fixed some minor spelling in the comments:

internal class Program
{
    /// <summary>
    /// Executes defined task within a specified time limits and returns a result
    /// of that execution. Result might not be completed fully.
    /// </summary>
    /// <typeparam name="T">Return type of a task</typeparam>
    /// <param name="timeSpan">Time span after which timeout occurs.</param>
    /// <param name="task">Task to execute within limits of time span.</param>
    /// <returns>Result of a given task. Result can be partial.</returns>
    private static async Task<T> ExecuteFor<T>(
        TimeSpan timeSpan,
        Func<CancellationToken, Task<T>> task)
    {
        using (var cts = new CancellationTokenSource(timeSpan))
        {
            return await task(cts.Token);
        }
    }
    
    private static async Task<IEnumerable<int>> GenerateNumbers(CancellationToken token)
    {
        List<int> numbers = [];

        while (token.IsCancellationRequested == false)
        {
            // Generate numbers until one second has passed
            await Task.Delay(Random.Shared.Next(70, 150));
            numbers.Add(Random.Shared.Next());
        }

        return numbers;
    }

    public static async Task Main(string[] args)
    {
        TimeSpan shortTimeSpan = TimeSpan.FromSeconds(2);
        TimeSpan longerTimeSpan = TimeSpan.FromSeconds(5);

        IEnumerable<int> result = await ExecuteFor(
            shortTimeSpan,
            GenerateNumbers);

        PrintResultIfAny(shortTimeSpan.Seconds, result);

        result = await ExecuteFor(
            longerTimeSpan,
            GenerateNumbers);

        PrintResultIfAny(longerTimeSpan.Seconds, result);
    }

    private static void PrintResultIfAny(int timeSpanInSeconds, IEnumerable<int> result)
    {
        // Print the result
        if (result is not null)
        {
            Console.WriteLine($"Numbers generated within time span " +
                $"of {timeSpanInSeconds} seconds:");

            foreach (int num in result)
            {
                Console.WriteLine(num);
            }
        }
        else
        {
            Console.WriteLine($"Execution canceled after {timeSpanInSeconds} seconds.");
        }
    }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.