我有一个依赖于另一个服务器的异步操作,它需要大部分随机时间才能完成。当异步操作正在运行时,“主线程”中也会进行处理,这也需要一段随机的时间来完成。
主线程启动异步任务,执行它的主要任务,并在最后检查异步任务的结果。
异步线程提取数据并计算对主线程不重要的字段。但是,如果计算能够在没有减慢主线程的情况下完成,那么这些数据会很好(并且应该包括在内)。
我想将异步任务设置为最少运行2秒,但是 在主要任务的开始和结束之间占用所有时间。 这是一个“懒惰的超时”,只有超过2才会超时 第二个运行时,实际上是请求结果。 (异步 任务应该花费2秒或更长的运行时间 主要任务)
编辑(尝试澄清要求):如果异步任务有机会运行2秒,则根本不应阻止主线程。主线程必须允许异步任务运行至少2秒。此外,如果主线程完成时间超过2秒,则应允许异步任务与主线程一起运行。
我设计了一个有效的包装器,但是我更喜欢实际上是Task类型的解决方案。请参阅下面的我的包装解决方案。
public class LazyTimeoutTaskWrapper<tResult>
{
private int _timeout;
private DateTime _startTime;
private Task<tResult> _task;
private IEnumerable<Action> _timeoutActions;
public LazyTimeoutTaskWrapper(Task<tResult> theTask, int timeoutInMillis, System.DateTime whenStarted, IEnumerable<Action> onTimeouts)
{
this._task = theTask;
this._timeout = timeoutInMillis;
this._startTime = whenStarted;
this._timeoutActions = onTimeouts;
}
private void onTimeout()
{
foreach (var timeoutAction in _timeoutActions)
{
timeoutAction();
}
}
public tResult Result
{
get
{
var dif = this._timeout - (int)System.DateTime.Now.Subtract(this._startTime).TotalMilliseconds;
if (_task.IsCompleted ||
(dif > 0 && _task.Wait(dif)))
{
return _task.Result;
}
else
{
onTimeout();
throw new TimeoutException("Timeout Waiting For Task To Complete");
}
}
}
public LazyTimeoutTaskWrapper<tNewResult> ContinueWith<tNewResult>(Func<Task<tResult>, tNewResult> continuation, params Action[] onTimeouts)
{
var result = new LazyTimeoutTaskWrapper<tNewResult>(this._task.ContinueWith(continuation), this._timeout, this._startTime, this._timeoutActions.Concat(onTimeouts));
result._startTime = this._startTime;
return result;
}
}
有没有人比这个包装器有更好的解决方案?
答案 0 :(得分:1)
我总是开始一个2秒的任务,当它完成时,将你的计算标记为已取消。这可以节省您奇怪的“差异”时间计算。这是一些代码:
Task mainTask = ...; //represents your main "thread"
Task computation = ...; //your main task
Task timeout = TaskEx.Delay(2000);
TaskCompletionSource tcs = new TCS();
TaskEx.WhenAll(timeout, mainTask).ContinueWith(() => tcs.TrySetCancelled());
computation.ContinueWith(() => tcs.TryCopyResultFrom(computation));
Task taskToWaitOn = tcs.Task;
这是伪代码。我只想展示这项技术。
TryCopyResultFrom用于通过调用TrySetResult()将calculate.Result复制到TaskCompletionSource tcs。
您的应用只使用taskToWaitOn。它将在2s后过渡到取消。如果计算提前完成,它将收到该结果。
答案 1 :(得分:1)
我认为你不能让Task<T>
以这种方式行事,因为Result
不是virtual
而且也没有任何其他方法可以改变其行为。
我也认为你甚至不应该尝试这样做。 Result
属性的合同是等待结果(如果它还不可用)并返回它。这不是取消任务。这样做会非常混乱。如果您要取消该任务,我认为从您正在执行的代码中可以看出这一点。
如果我这样做,我会为Task<T>
创建一个包装器,但它看起来像这样:
class CancellableTask<T>
{
private readonly Func<CancellationToken, T> m_computation;
private readonly TimeSpan m_minumumRunningTime;
private CancellationTokenSource m_cts;
private Task<T> m_task;
private DateTime m_startTime;
public CancellableTask(Func<CancellationToken, T> computation, TimeSpan minumumRunningTime)
{
m_computation = computation;
m_minumumRunningTime = minumumRunningTime;
}
public void Start()
{
m_cts = new CancellationTokenSource();
m_task = Task.Factory.StartNew(() => m_computation(m_cts.Token), m_cts.Token);
m_startTime = DateTime.UtcNow;
}
public T Result
{
get { return m_task.Result; }
}
public void CancelOrWait()
{
if (m_task.IsCompleted)
return;
TimeSpan remainingTime = m_minumumRunningTime - (DateTime.UtcNow - m_startTime);
if (remainingTime <= TimeSpan.Zero)
m_cts.Cancel();
else
{
Console.WriteLine("Waiting for {0} ms.", remainingTime.TotalMilliseconds);
bool finished = m_task.Wait(remainingTime);
if (!finished)
m_cts.Cancel();
}
}
}
请注意,计算具有CancellationToken
参数。那是因为你不能强制取消(没有像Thread.Abort()
这样的肮脏技巧)并且计算必须明确地支持它,理想情况是在适当的时候执行cancellationToken.ThrowIfCancellationRequested()
。