【问题标题】:Async Lazy Timeout Task异步延迟超时任务
【发布时间】:2012-02-04 16:49:52
【问题描述】:

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

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

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

我想将异步任务设置为至少运行 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 秒,但如果主线程太忙而无法立即需要结果,则主线程希望允许它运行超过 2 秒。如果主线程完成时间超过 2 秒,则主线程根本不想等待。

标签: c# asynchronous multitasking


【解决方案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() 将计算结果复制到 TaskCompletionSource tcs。

您的应用只使用 taskToWaitOn。 2s 后转为取消。如果计算提前完成,它将收到计算结果。

【讨论】:

  • 我从未见过TryCopyResultFrom(),显然谷歌也没有。这个从哪里来?当然不是来自 .Net(无论是 4.0 还是 4.5)。
  • 另外,我认为这不是被问到的。仅当主线程已经完成时,才应在 2 秒后取消计算。
  • seconding svick 至少需要等待 2 秒,如果主线程需要 100 秒,则需要等待 100 秒。
  • 我已经更新了代码。它现在等待超时 mainTask。我还澄清了 TryCopyResultFrom 的含义。请记住,这是伪代码。您必须自己完成这项工作。我只是想告诉你它是如何工作的。
【解决方案2】:

我认为你不能让Task&lt;T&gt; 表现出这种行为,因为Result 不是virtual,而且也没有其他方法可以改变它的行为。

我也认为你甚至不应该尝试这样做。 Result 属性的约定是等待结果(如果它还不可用)并返回它。不是取消任务。这样做会很混乱。如果您要取消任务,我认为从您正在执行的代码中应该很明显。

如果我要这样做,我会为 Task&lt;T&gt; 创建一个包装器,但它看起来像这样:

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

【讨论】:

  • 查看我的顶级编辑,您希望如何实现这些要求?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-02-02
  • 2011-05-19
  • 1970-01-01
相关资源
最近更新 更多