使用超时创建任务

时间:2013-12-21 09:17:06

标签: .net c#-4.0 asynchronous timeout task-parallel-library

我想要执行一系列任务,每个任务都有自己的超时。

我从这里借用了用于创建超时任务的扩展方法 http://blogs.msdn.com/b/pfxteam/archive/2011/11/10/10235834.aspx

所以代码在

之下
 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

现在,我无法理解的是,即使我指定了超时并且发生了超时异常,为什么t1仍然完成。

我正在使用.net 4。

编辑:

确保超时期限后超时任务不执行任何操作,即完全取消任务。

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();
        }
    }

输出:

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 个答案:

答案 0 :(得分:2)

输出包含三个值,因为程序等待所有任务Task.WaitAll(tasks.ToArray());而输出是公共字段(因为关闭)

您只能保留第一项任务,并且您会看到另一项结果

Entering task1
Managed thread Id 10
There was an exception
The operation has timed out.
Output :
Adding 1923041190
Managed thread Id 10

请注意,Adding已拨打电话,但Output中没有此号码。调用Adding是因为LongRunningTask在此任务Task.Factory.StartNew(_ => LongRunningTask("Entering task1"), TaskCreationOptions.AttachedToParent)中起作用,并且异常已在不同的线程上抛出。此例外不会对LongRunningTask

产生影响

修改

有几种选择:

  1. 调用t1.Wait将立即重新抛出异常,您可以取消任务
  2. 在ContinueWith

    之前调用TimeoutAfter(10)
        var t1 = Task.Factory.StartNew(() => LongRunningTask("Entering task1"))
                             .TimeoutAfter(10)
                             .ContinueWith(_=> Output.Add(_.Result), cts.Token);
    
  3. Continue只有在完成TimeoutAfterLongRunningTask后才能执行,但您必须更新TimeoutAfter,否则您必须Task<Result>而不是{{} 1}}

    Task

答案 1 :(得分:2)

TimeoutAfter()方法对基础Task没有任何作用。因此,即使发生超时,Task仍会继续执行并最终完成。

没有修改LongRunningTask(),没有好办法解决这个问题。如果您可以修改LongRunningTask(),那么您应该接受CancellationToken并在适当的位置进行检查。

ContinueWith()您的Task尝试没有改变任何内容,因为var t1 = Task.Factory.StartNew(() => LongRunningTask("Entering task1")) .TimeoutAfter(1000) .ContinueWith(t => Output.Add(t.Result), cts.Token); 仍然完成,因此继续发布。

会有什么帮助:

t1

如果你这样做,那么t将表示延续,因此如果超时发生则会出现故障(等待它会引发异常)。如果您不想这样,请在访问Result之前检查续集中Add()的状态。

此外,你永远不应该像List这样调用Add(),因为{{1}}不是线程安全的,并且多个线程有可能会尝试添加它同时。为避免这种情况,请使用其中一个并发集合或锁定。

答案 2 :(得分:0)

仅供参考, 我最终做了这样的事情

var t1 = Task.Factory.StartNew(_ => LongRunningTask("Entering task1"),                              TaskCreationOptions.AttachedToParent)
                                     .TimeoutAfter(1000)
                                     .ContinueWith(_ =>
                                {
                                    if(!(_.IsCanceled || _.IsFaulted))
                                        Output.Add(_.Result);
                                }
                                , cts.Token);

答案 3 :(得分:0)

即使我找到了之前的答案,我也有一个实现,我发现使用基于事件的框架很容易实现。

让我解释一下这个要求,我必须将所有内容包装在可能需要超过50 MS的异步中,以便用户与屏幕的交互保持流畅。因此,我需要包装所有套接字请求和对服务器的响应。这些类型的编程通常涉及请求某些东西,然后得到答案,请求和答案不需要跟随FIFO,因为人们可以问,黄金的价格是多少,每秒数百次获取数据,然后询问我的帐户是什么值。

这是我的实现,我添加了一些评论,让一些人更容易理解。

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;

}