2012-02-04 80 views
4

我有一个异步操作取决于另一台服务器,它需要大部分随机的时间来完成。在异步操作正在运行的同时,“主线程”中还有一些处理正在进行,这也需要花费一定的时间来完成。异步延迟超时任务

主线程启动异步任务,执行它的主要任务,并在最后检查异步任务的结果。

异步线程拉取数据并计算对主线程完成不重要的字段。然而,如果计算能够完成而没有减慢的主线程,则该数据将会很好(并且应该被包括)。

我想设置异步任务,在最小运行2秒,但 采取一切开始和主要任务结束之间的可用时间。 这是一个'延迟超时',因为如果超过第二个运行时间并且实际上正在请求结果,则它仅超时。 (异步 任务应采取更大的2秒,或 主要任务的总运行时间)

EDIT(试图澄清的要求):如果异步任务有机会运行持续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; 
    } 
} 

有没有人比这个包装有更好的解决方案?

+1

我很好奇你为什么想要后台任务花费比完成实际工作所需的时间更长的时间。 – 2012-02-04 17:12:19

+0

这不应该花费比实际工作时间更长的时间。主线程只需要等待至多2秒,但如果主线程太忙而不能立即需要结果,则主线程要允许它运行超过2秒。如果主线程花费了2秒多的时间完成,主线程根本不想等待。 – hannasm 2012-02-04 17:28:54

回答

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是为了将计算结果复制到TaskCompletionSource tcs中,方法是调用TrySetResult()。

你的应用程序只是使用taskToWaitOn。它会过渡到2秒后取消。如果计算较早完成,它将收到结果。

+0

我从来没有见过'TryCopyResultFrom()',显然谷歌也没有。这个从哪里来?当然不是来自.Net(无论是4.0还是4.5)。 – svick 2012-02-04 17:22:18

+1

另外,我认为这不是问什么。只有在主线程已经完成的情况下,计算应在2秒后取消。 – svick 2012-02-04 17:23:15

+0

借调svick它需要至少等待2秒,如果主线程需要100秒,则需要100秒。 – hannasm 2012-02-04 17:26:52

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()

+0

查看我的顶级编辑,您希望如何实现这些要求? – hannasm 2012-02-04 17:41:21

+0

查看我的编辑,了解我可能如何做到这一点。 – svick 2012-02-04 18:08:30