Async Lazy Timeout Task

I have an async operation depending on another server, which takes a mostly random amount of time to complete. While the asynchronous operation is in progress, processing also takes place in the "main thread", which also takes an arbitrary amount of time to complete.

The main thread starts the asynchronous task, executes its main task, and checks the result of the asynchronous task at the end.

An asynchronous stream pulls data and calculates fields that are not critical to terminating the main stream. However, this data would be good to have (and should be included) if the calculation could be completed without slowing down the main thread.

I would like to configure the async task to run for at least 2 seconds, but so that all the time is available between the beginning and end of the main task. This is a "lazy timeout" in that it only timeouts if 2 second runtimes are exceeded and the result is actually queried. (Asynchronous task should take more than 2 seconds or the total duration of the main task)

EDIT (trying to clarify the requirements): If the async task was able to run within 2 seconds, it should not block the main thread. The main thread must allow the asynchronous task to run for at least 2 seconds. In addition, if the main thread takes more than 2 seconds, the async task should run until the main thread.

I developed a shell that works, however I would prefer a solution that is actually of type Task. See my shell solution below.

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; } } 

Does anyone have a better solution than this shell?

+4
source share
2 answers

I would always start a 2 second task, which, when it completes, marks your calculations as canceled. This saves you a weird scatter schedule. Here is the code:

 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; 

This is pseudo code. I just wanted to show the technique.

TryCopyResultFrom is for copying calculations. Return to the TaskCompletionSource object by calling TrySetResult ().

Your application simply uses taskToWaitOn. It will switch to cancel after 2 seconds. If the calculation completes earlier, it will get the result of this.

+1
source

I don’t think you can do Task<T> this way, because Result not virtual , and there is no other way to change its behavior.

I also think that you should not even try to do this. The Result property contract must wait for the result (if it is not already available) and return it. This does not cancel the task. This will be very difficult. If you cancel the task, I think it should be obvious from the code you are doing.

If I did this, I would create a wrapper for Task<T> , but it would look like this:

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

Note that the calculation has a CancellationToken parameter. This is because you cannot forcefully invalidate (without dirty tricks such as Thread.Abort() ), and the calculation should explicitly support it, ideally by doing cancellationToken.ThrowIfCancellationRequested() at the appropriate time.

+1
source

Source: https://habr.com/ru/post/1394772/


All Articles