如何执行任务<t>并对其进行超时?</t>

时间:2013-11-27 13:22:01

标签: c# task-parallel-library

我的其他问题基本上需要这个,所以我想我会在这里分享我的通用解决方案。

我在HttpClient任务上遇到麻烦,因为Web请求基本上没有完成;所以程序或线程挂起。我需要一种简单的方法来为任务添加超时,以便正常返回或在超时首先到期时返回取消。

这里可以找到一些替代方法: http://blogs.msdn.com/b/pfxteam/archive/2011/11/10/10235834.aspx

3 个答案:

答案 0 :(得分:4)

您在技术上无法承担任务并在一段时间后强制取消该任务。您可以做的最好的事情是创建一个新任务,该任务将在给定的时间段后标记为已取消,或者在另一个任务完成时完成。

要注意的一个关键点是,操作超时并不能阻止任务继续工作,它只会触发这项新任务的所有延续&#34;早期&#34 ;如果达到超时。

由于使用CancellationToken

,这样做非常简单
var newTask = task.ContinueWith(t => { }
    , new CancellationTokenSource(timeoutTime).Token);

我们可以将此模式与WhenAny结合起来,以便轻松确保将任何异常正确传播到延续。还需要有/无结果的任务副本:

public static Task WithTimeout(Task task, TimeSpan timeout)
{
    var delay = task.ContinueWith(t => { }
        , new CancellationTokenSource(timeout).Token);
    return Task.WhenAny(task, delay).Unwrap();
}
public static Task<T> WithTimeout<T>(Task<T> task, TimeSpan timeout)
{
    var delay = task.ContinueWith(t => t.Result
        , new CancellationTokenSource(timeout).Token);
    return Task.WhenAny(task, delay).Unwrap();
}

答案 1 :(得分:0)

我提出了这种扩展方法;为了防止System.Threading.Timer被垃圾收集,我最终在Task的AsyncState字段中存储了一个引用。

public static class Extensions
{
    public static Task<T> TaskWithTimeOut<T>(this Task<T> Task, TimeSpan TimeOut)
    {
        TaskCompletionSource<T> _tCS = null;

        System.Threading.Timer _timer = null;

        // create enclosure that captures the Timer and TaskCompletionSource
        TimerCallback _timerCallBack = delegate(Object State) { _tCS.TrySetCanceled(); _timer.Dispose(); };

        //timer now holds reference to _tCS (and itself) via the delegate. Don't start the timer yet because it is
        //still null.
        _timer = new System.Threading.Timer(_timerCallBack, null, System.Threading.Timeout.InfiniteTimeSpan, System.Threading.Timeout.InfiniteTimeSpan);

        //put the _timer reference into the task's state; now the program holds on to a reference to the Timer and thereby to
        //the TaskCompletionState
        _tCS = new TaskCompletionSource<T>(_timer); // contains reference to _tCS via enclosure

        _timer.Change(TimeOut, System.Threading.Timeout.InfiniteTimeSpan);

        // a fire-and-forget continuewith
        Task.ContinueWith(
            _task_ =>
            {
                TaskStatus _status = _task_.Status;

                switch (_status)
                {
                    case TaskStatus.RanToCompletion:
                        _tCS.TrySetResult(_task_.Result);
                        break;
                    case TaskStatus.Canceled:
                        _tCS.TrySetCanceled();
                        break;
                    case TaskStatus.Faulted:
                        _tCS.TrySetException(_task_.Exception);
                        break;
                }

                (_task_.AsyncState as System.Threading.Timer).Dispose();

            }, TaskContinuationOptions.ExecuteSynchronously);

        return _tCS.Task;
    }
}

像这样使用:

Task<HttpResponseMessage> _response = httpClient.PostAsJsonAsync<RequestBodyType>(target, RequestBodyTypeInstance).TaskWithTimeOut<HttpResponseMessage>(httpClient.Timeout);

答案 2 :(得分:0)

超时时,您可以使用CancellationTokenSource:

var DefualtTimeout = 5000;
var cancelToken = new CancellationTokenSource();

var taskWithTimeOut = Task.Factory.StartNew( t =>
                           {
                              var token = (CancellationToken)t;
                              while (!token.IsCancellationRequested)
                              {
                                 // Here your work
                              }
                              token.ThrowIfCancellationRequested();
                           }, cancelToken.Token, cancelToken.Token);

方法1:

   if (!taskWithTimeOut.Wait(DefualtTimeout, cancelToken.Token)) 
      {
         cancelToken.Cancel();
      }

方法2:

SpinWait.SpinUntil(() => taskWithTimeOut.IsCompleted, new TimeSpan(0, 0, 0, 0, DefualtTimeout ));

方法3:

您可以使方法异步,在任务写入await taskWithTimeOut.Wait..之前,UI不会被阻止。