2014-09-23 75 views
3

我有我的web请求处理此代码;如何为不接受取消标记的异步函数设置超时值?

Response = await Client.SendAsync(Message, HttpCompletionOption.ResponseHeadersRead, CToken); 

在读取响应标题之后并在内容读完之前返回。当我称这条线来获取内容...

return await Response.Content.ReadAsStringAsync(); 

我希望能够在X秒后停止它。但它不接受取消令牌。

+1

仅供参考,如果你处理的IDisposable的超时你可能想看看这个:[异步网络运营写不完](HTTP ://stackoverflow.com/a/21468138/885318) – i3arnon 2014-09-23 06:09:07

回答

4

虽然你可以依靠WithCancellation再利用的目的,超时简单的解决方案(不扔OperationCanceledException)是创建一个超时任务与Task.Delay,等待第一个任务使用Task.WhenAny完成:

public static Task<TResult> WithTimeout<TResult>(this Task<TResult> task, TimeSpan timeout) 
{ 
    var timeoutTask = Task.Delay(timeout).ContinueWith(_ => default(TResult), TaskContinuationOptions.ExecuteSynchronously); 
    return Task.WhenAny(task, timeoutTask).Unwrap(); 
} 

或者,如果您想在发生超时而不是仅仅返回默认值的情况下抛出异常(即, null):

public static async Task<TResult> WithTimeout<TResult>(this Task<TResult> task, TimeSpan timeout) 
{ 
    if (task == await Task.WhenAny(task, Task.Delay(timeout))) 
    { 
     return await task; 
    } 
    throw new TimeoutException(); 
} 

而且用法是:

var content = await Response.Content.ReadAsStringAsync().WithTimeout(TimeSpan.FromSeconds(1)); 
+2

虽然NedStoyanov的答案也很棒,但这个代码专门提供了处理超时的代码,而不仅仅是允许取消。我觉得这个更好地回答了这个问题。 – iguanaman 2014-09-24 09:54:31

+0

但是,是否有任何担保延迟任务将不会安排在任务之前?任务可能没有问题,但是任务调度程序仍然可以首先运行Delay(如果所有任务容量已满,则必须选择)? – 2017-02-01 10:43:41

+0

@DavidS。 'Task.Delay'并没有真正的计划,因为它并不真正“运行”。它只是启动一个计时器,在超时后弹出并完成任务。而且,你得到的任务不需要“开始”......它已经很热,所以这里没有真正的担心(除非在极端情况下)。 – i3arnon 2017-02-01 11:08:07

2

看看How do I cancel non-cancelable async operations?。如果您只想在请求在后台继续时完成await,则可以使用作者的WithCancellation扩展方法。这是从文章转载:

public static async Task<T> WithCancellation<T>( 
    this Task<T> task, CancellationToken cancellationToken) 
{ 
    var tcs = new TaskCompletionSource<bool>(); 
    using(cancellationToken.Register( 
       s => ((TaskCompletionSource<bool>)s).TrySetResult(true), tcs)) 
     if (task != await Task.WhenAny(task, tcs.Task)) 
      throw new OperationCanceledException(cancellationToken); 
    return await task; 
} 

它在本质上与接受取消标记,然后用Task.WhenAny等待两个任务的任务合并了原先的任务。所以当你取消CancellationToken时,secodn任务被取消,但原来的任务继续进行。只要你不关心你可以使用这种方法。

您可以使用它像这样:

return await Response.Content.ReadAsStringAsync().WithCancellation(token); 

更新

您也可以尝试处置响应,取消的一部分。

token.Register(Reponse.Content.Dispose); 
return await Response.Content.ReadAsStringAsync().WithCancellation(token); 

现在随着您取消令牌,Content对象将被丢弃。

+0

非常感谢,没有办法阻止正在进行的ReadAsStringAsync呢?这是一个很好的解决方案,但理想情况下,如果可能的话,我想中止ReadAsStringAsync。 – iguanaman 2014-09-23 02:17:50

+0

你可以使用'Thread.Abort'作为这个答案http://stackoverflow.com/a/21715122/1239433,但是你可能需要在Task.Run中调用一个同步版本的函数来完成它。 – 2014-09-23 03:13:56

+2

@iguanaman:传统上,如果底层句柄已关闭,则Microsoft API将取消它们的I/O操作。所以,当取消令牌被发信号时,你可以尝试处置'Response.Content'和/或'Response'。 – 2014-09-23 11:11:04

-2

由于回报的任务,你可以Wait的任务基本上等同于指定超时:

// grab the task object 
var reader = response.Content.ReadAsStringAsync(); 

// so you're telling the reader to finish in X milliseconds 
var timedOut = reader.Wait(X); 

if (timedOut) 
{ 
    // handle timeouts 
} 
else 
{ 
    return reader.Result; 
} 
+1

这使得它同步,这是不理想的。 – 2014-09-23 02:57:06

+0

只读部分。无论是否可接受,都基于哪些更重要的因素 - 能够超时或保持异步而无需控制流程。 – Mrchief 2014-09-23 03:04:25