如何获取Task 并对其进行超时?
我的其他问题基本上需要这个,所以我想我会在这里分享我的通用解决方案。
我在HttpClient任务上遇到麻烦,因为Web请求基本上没有完成; 所以程序或线程挂起。 我需要一种简单的方法来为任务添加超时,以便正常返回或在超时首先到期时返回取消。
可以在这里找到一些替代方法: http : //blogs.msdn.com/b/pfxteam/archive/2011/11/10/10235834.aspx
从技术上讲,您无法承担任务并在一段时间后强制取消该任务。 您可以做的最好的事情是创建一个新任务,该任务将在给定的时间段后标记为已取消,或者在其他任务完成时完成,如果它及时完成。
需要注意的一个关键点是,操作超时不会阻止任务继续执行它的工作,如果达到超时,它只会“提前”触发此新任务的所有延续。
由于使用了CancellationToken
,这样做非常简单:
var newTask = task.ContinueWith(t => { } , new CancellationTokenSource(timeoutTime).Token);
我们可以将此模式与WhenAny
结合起来,以便轻松确保将任何exception正确传播到延续。 还需要有/无结果的任务副本:
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 WithTimeout (Task task, TimeSpan timeout) { var delay = task.ContinueWith(t => t.Result , new CancellationTokenSource(timeout).Token); return Task.WhenAny(task, delay).Unwrap(); }
超时到来时可以使用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就不会被阻止。
我想出了这种扩展方法; 为了防止System.Threading.Timer被垃圾收集我最终在Task的AsyncState字段中存储了一个引用。
public static class Extensions { public static Task TaskWithTimeOut (this Task Task, TimeSpan TimeOut) { TaskCompletionSource _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 (_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 _response = httpClient.PostAsJsonAsync(target, RequestBodyTypeInstance).TaskWithTimeOut(httpClient.Timeout);