我的其他问题基本上需要这个,所以我想我会在这里分享我的通用解决方案。
我在HttpClient任务上遇到麻烦,因为Web请求基本上没有完成;所以程序或线程挂起。我需要一种简单的方法来为任务添加超时,以便正常返回或在超时首先到期时返回取消。
这里可以找到一些替代方法: http://blogs.msdn.com/b/pfxteam/archive/2011/11/10/10235834.aspx
答案 0 :(得分:4)
您在技术上无法承担任务并在一段时间后强制取消该任务。您可以做的最好的事情是创建一个新任务,该任务将在给定的时间段后标记为已取消,或者在另一个任务完成时完成。
要注意的一个关键点是,操作超时并不能阻止任务继续工作,它只会触发这项新任务的所有延续"早期&#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不会被阻止。