2014-09-23 72 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