如何对Task<T>设置超时时间?

3

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

我在使用HttpClient任务进行Web请求时遇到了麻烦,因为它们基本上永远不会完成;所以程序或线程会挂起。我需要一种简单的方法来为任务添加超时,以便如果超时先过期,则正常返回或返回已取消。

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


1
AZ最近有发布问题吗?它提供了“分享您的知识”的选项,让您回答自己的问题。 - Elliot
5
为什么你认为发布并回答自己的问题不是好习惯?“提问”页面中甚至有一个“回答自己的问题”的复选框。 - dcastro
2
如果你认为这是不良习惯,你应该在元站上讨论你的观点。当前实现实际上支持回答自己的问题 - Martin Liversage
或者您可以使用像这样的简单解决方案 https://dev59.com/um855IYBdhLWcg3woVw_#11191070 - Honza Brestan
4
@AZ并不是在请求代码审查,他是在分享知识。他声称找到了一个解决一个普遍问题的方法,而在Stack Overflow上并没有令人满意的答案。无论这个声称是否有效,那都是另一个问题。 - dcastro
显示剩余7条评论
2个回答

3

从技术上讲,您无法在一段时间后强制取消任务。最好的方法是创建一个新任务,在给定的时间段后将其标记为已取消,或者如果其他任务及时完成,则标记为已完成。

需要注意的关键点是,操作超时并不会停止任务继续工作,只有在达到超时时间时,所有这个新任务的延续才会“提前”触发。

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

是的,但它不会超时底层任务,所以这不是一个解决方案。 - Elliot
2
@Elliot,你提供的解决方案也不行。就像我在回应你的解决方案时所说的那样,在一般情况下解决这个问题是不可能的。这个答案做了你的答案要做的事情,但只用了一行代码而不是45行。虽然它们在功能上是相同的。 - Servy
抱歉Servy,我知道不可调整大小的框很烦人,但你应该再读一遍我的解决方案。 - Elliot
我并不是说它会取消底层操作;一个永无止境的任务仍然会是永无止境的。我需要的是在等待任务时,这种情况不会阻塞程序。ContinueWith 只有在底层操作完成时才会运行(或取消),这是不同的。 - Elliot
@Elliot 不,那不是真的。只要取消令牌表示已取消,由继续操作产生的任务将立即标记为已取消,即使该任务尚未完成。 - Servy
显示剩余12条评论

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

方法一:

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

方法二:

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

方法三:

您可以将该方法设置为异步,并在任务之前写入 await taskWithTimeOut.Wait..,这样UI就不会被阻塞。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接