异步延迟超时任务

时间:2012-02-04 16:49:53

标签: c# asynchronous multitasking

我有一个依赖于另一个服务器的异步操作,它需要大部分随机时间才能完成。当异步操作正在运行时,“主线程”中也会进行处理,这也需要一段随机的时间来完成。

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

异步线程提取数据并计算对主线程不重要的字段。但是,如果计算能够在没有减慢主线程的情况下完成,那么这些数据会很好(并且应该包括在内)。

  

我想将异步任务设置为最少运行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;
    }
}

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

2 个答案:

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