Creating Tasks with timeouts

Viewed 5666

I wanted to have a series of tasks being executed each with a timeout of it's own.

I borrowed the extension method for creating tasks with timeouts from here http://blogs.msdn.com/b/pfxteam/archive/2011/11/10/10235834.aspx

So the code is below

 public static Task TimeoutAfter(this Task task, int millisecondsTimeout)
        {
            // Short-circuit #1: infinite timeout or task already completed
            if (task.IsCompleted || (millisecondsTimeout == Timeout.Infinite))
            {
                // Either the task has already completed or timeout will never occur.
                // No proxy necessary.
                return task;
            }

            // tcs.Task will be returned as a proxy to the caller
            TaskCompletionSource<VoidTypeStruct> tcs = new TaskCompletionSource<VoidTypeStruct>();

            // Short-circuit #2: zero timeout
            if (millisecondsTimeout == 0)
            {
                // We've already timed out.
                tcs.SetException(new TimeoutException());
                return tcs.Task;
            }

            // Set up a timer to complete after the specified timeout period
            Timer timer = new Timer(state =>
            {
                // Recover your state information
                var myTcs = (TaskCompletionSource<VoidTypeStruct>)state;
                // Fault our proxy with a TimeoutException
                myTcs.TrySetException(new TimeoutException());
            }, tcs, millisecondsTimeout, Timeout.Infinite);

            // Wire up the logic for what happens when source task completes
            task.ContinueWith(antecedent =>
                                {
                                    timer.Dispose(); // Cancel the timer
                                    MarshalTaskResults(antecedent, tcs); // Marshal results to proxy
                                },
                                CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);

            return tcs.Task;
        }

public class Program
    {
        private static List<int> Output = new List<int>();

        private static Random _random = new Random();
        public static void LongRunningTask(string message)
        {
            Console.WriteLine(message);
            Console.WriteLine("Managed thread Id " + Thread.CurrentThread.ManagedThreadId);            
            //Simulate a long running task
            Thread.Sleep(TimeSpan.FromSeconds(3));
            var number = _random.Next();
            Console.WriteLine("Adding " + number);
            Output.Add(number);
        }
        public static void Main(string[] args)
        {
            var tasks = new List<Task>();

            var t1 = Task.Factory.StartNew(_ => LongRunningTask("Entering task1"),TaskCreationOptions.AttachedToParent).TimeoutAfter(10);
            var t2 = Task.Factory.StartNew(_ => LongRunningTask("Entering task2"),TaskCreationOptions.AttachedToParent);
            var t3 = Task.Factory.StartNew(_ => LongRunningTask("Entering task3"),TaskCreationOptions.AttachedToParent);

            tasks.Add(t1);
            tasks.Add(t2);
            tasks.Add(t3);

            try
            {
                Task.WaitAll(tasks.ToArray());
            }
            catch (Exception ex)
            {
                Console.WriteLine("There was an exception");
                Console.WriteLine(ex.InnerException.Message);
            }

            Console.WriteLine("Output :");
            Output.ForEach(_ => Console.WriteLine(_));

            Console.ReadLine();
        }
    }



the output 

    Entering task1
    Managed thread Id 10
    Entering task2
    Managed thread Id 11
    Entering task3
    Managed thread Id 14
    Adding 453738994
    Adding 156432981
    Adding 1340619865
    There was an exception
    The operation has timed out.
    Output :
    453738994
    156432981
    1340619865

Now, what I can't understand is why is the t1 still finishing even though I have specified a timeout and the timeout exception has occurred.

I am using .net 4.

Edit :

Ensuring that the timed out task doesn't do anything after the timeout period i.e. cancelling the task altogether.

public class Program
    {
        private static List<int> Output = new List<int>();

        private static Random _random = new Random();
        public static int LongRunningTask(string message)
        {
            Console.WriteLine(message);
            Console.WriteLine("Managed thread Id " + Thread.CurrentThread.ManagedThreadId);            
            //Simulate a long running task
            Thread.Sleep(TimeSpan.FromSeconds(2));
            var number = _random.Next();
            Console.WriteLine("Adding " + number + " From thread  - " + Thread.CurrentThread.ManagedThreadId);
            return number;
        }
        public static void Main(string[] args)
        {
            Console.WriteLine("In Main");
            Console.WriteLine("Managed thread Id " + Thread.CurrentThread.ManagedThreadId);
            var cts = new CancellationTokenSource();
            var tasks = new List<Task>();

            var t1 = Task.Factory.StartNew(_ => LongRunningTask("Entering task1"), TaskCreationOptions.AttachedToParent)
                                 .ContinueWith(_ => Output.Add(_.Result),cts.Token)
                                 .TimeoutAfter(1000);
            var t2 = Task.Factory.StartNew(_ => LongRunningTask("Entering task2"), TaskCreationOptions.AttachedToParent)
                                 .ContinueWith(_ => Output.Add(_.Result));
            var t3 = Task.Factory.StartNew(_ => LongRunningTask("Entering task3"), TaskCreationOptions.AttachedToParent)
                                 .ContinueWith(_ => Output.Add(_.Result));

            tasks.Add(t1);
            tasks.Add(t2);
            tasks.Add(t3);

            try
            {
                Task.WaitAll(tasks.ToArray());
            }
            catch (Exception ex)
            {
                Console.WriteLine("There was an exception");
                Console.WriteLine(ex.InnerException.Message);
                cts.Cancel();
            }

            Console.WriteLine("Output :");
            Output.ForEach(_ => Console.WriteLine(_));

            Console.ReadLine();
        }
    }

Output :

In Main
Managed thread Id 9
Entering task1
Managed thread Id 10
Entering task2
Managed thread Id 11
Entering task3
Managed thread Id 13
Adding 1141027730 From thread  - 10
Adding 1856518562 From thread  - 13
Adding 1856518562 From thread  - 11
There was an exception
The operation has timed out.
Output :
1141027730
1856518562
1856518562
4 Answers

Even though I find the previous answer to be find, I have a implementation that I find quite easy to implement with my event based framework.

Let me explain the requirement, I have to wrap all in async that could take longer than 50 MS so that user interactions with the screen stays fluid. All my socket requests and response to the server therefore need to be wrapped. these type of programming typical involves requesting something and then getting an answer the requests and answers do not need to follow FIFO as one can ask, what's the price of gold and get the data flowing in several hundred times per second and then ask what's my accounts value.

Here is my implementation, I added some comments making it easier for some to follow.

internal Task<string[]> RequestAccountNamesAsync() => RequestAccountNamesAsync(-1);
internal Task<string[]> RequestAccountNamesAsync(int millisecondsTimeout) => RequestAccountNamesAsync(CancellationToken.None, millisecondsTimeout);
internal Task<string[]> RequestAccountNamesAsync(CancellationToken token,int millisecondsTimeout = 1000 )
{

    var t1 =  Task.Factory.StartNew<string[]>( () =>
    {
        try
        {
            //the result type of the Task
            string[] result = null;

            //local helper function used to hookup the event
            void Method(object sender, OnAccountsReceivedArgs ac)
            {
                this.OnAccountsReceived -= Method;
                result = ac.Accounts;
            }

            //event responsible for reacting on the "on complete event"
            this.OnAccountsReceived += Method;



            //item responsible for initiating the socket request
            clientSocket.reqManagedAccts();

            //measure time-out 
            DateTimeOffset startTime = DateTimeOffset.Now;

            //loop waiting for the result to come from the server
            while (result == null)
            {
                if (millisecondsTimeout > 0 && DateTimeOffset.Now.Subtract(startTime).Milliseconds >= millisecondsTimeout)
                    throw new TimeoutException();
                //if the value of the millisecondsTimeout argument is zero, the thread relinquishes the remainder of its 
                // time slice to any thread of equal priority that is ready to run
                // If there are no other threads of equal priority that are ready to run, execution of the current thread
                // is not suspended. 
                Thread.Sleep(0);
            }
            return result;
        }
        catch (Exception e)
        {                    
            //integrate my proprietary logging framework
            logger.Enqueue<IBClient>(e);
            throw e;
        }
    });

    return t1;

}
Related