如何获取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);