Long running task with Task.Run - c#

I am running an infinite loop of updates using Task.Run. This is the code:
Task.Run( async () => {
while(true){
Thread.Sleep(10000);
checkForUpdates();
}
});
In the old days, I used to write special code for long running tasks. Does the duration of of a task execution matter with Task.Run? Is there a timeout built in?

No ! there is no built-in timeout for Task.Run(). That means it will continue running until you manually cancel it. And you can implement that by doing something like this.
int timeout = 1000;
var task = SomeOperationAsync(cancellationToken);
if (await Task.WhenAny(task, Task.Delay(timeout, cancellationToken)) == task)
{
// Task completed within timeout.
// Consider that the task may have faulted or been canceled.
// We re-await the task so that any exceptions/cancellation is rethrown.
await task;
}
else
{
// timeout/cancellation logic
}
you can checkout this blog for more understanding Crafting a task time-out

Related

Using Task.Delay within Task.Run

I have a Windows Service that monitors my application by running a couple of tests every second. A bug report has been submitted that said that the service stoppes running after a while, and I'm trying to figure out why.
I suspect that the code below is the culprit, but I have trouble understanding exactly how it works. The ContinueWith statement has recently been commented out, but I dont know if it is needed
private Task CreateTask(Action action)
{
var ct = _cts.Token;
return Task.Run(async () =>
{
ct.ThrowIfCancellationRequested();
var sw = new Stopwatch();
while (true)
{
sw.Restart();
action();
if (ct.IsCancellationRequested)
{
_logger.Debug("Cancellation requested");
break;
}
var wait = _settings.loopStepFrequency - sw.ElapsedMilliseconds;
if (wait <= 0) // No need to delay
continue;
// If ContinueWith is needed wrap this in an ugly try/catch
// handling the exception
await Task.Delay(
(int)(_settings.loopStepFrequency - sw.ElapsedMilliseconds),
ct); //.ContinueWith(tsk => { }, ct);
}
_logger.Debug("Task was cancelled");
}, _cts.Token);
}
Are there any obvious problems with this code?
Are there any obvious problems with this code?
The one that jumps out to me is the calculation for the number of milliseconds to delay. Specifically, there's no floor. If action() takes an unusually long time, then the task could fail in a possibly unexpected way.
There are several ways for the task to complete in either a cancelled or failed state, or it can delay forever:
The task can be cancelled before the delegate begins, due to the cancellation token passed to Task.Run.
The task can be cancelled by the ThrowIfCancellationRequested call.
The task can complete successfully after being cancelled, due to the IsCancellationRequested logic.
The task can be cancelled by the cancellation token passed to Task.Delay.
The task may fail with an ArgumentOutOfRangeException if _settings.loopStepFrequency - sw.ElapsedMilliseconds is less than -1. This is probably a bug.
The task may delay indefinitely (until cancelled) if _settings.loopStepFrequency - sw.ElapsedMilliseconds happens to be exactly -1. This is probably a bug.
To fix this code, I recommend two things:
The code is probably intending to do await Task.Delay((int) wait, ct); instead of await Task.Delay((int)(_settings.loopStepFrequency - sw.ElapsedMilliseconds), ct);. This will remove the last two conditions above.
Choose one method of cancellation. The standard pattern to express cancellation is via OperationCanceledExcpetion; this is the pattern used by ThrowIfCancellationRequested and by Task.Delay. The IsCancellationRequested check is using a different pattern; it will successfully complete the task on cancellation, instead of cancelling it.
There are so many problems with this code, that makes more sense to rewrite it than attempt to fix it. Here is a possible way to rewrite this method, with some (possibly superfluous) argument validation added:
private Task CreateTask(Action action)
{
if (action == null) throw new ArgumentNullException(nameof(action));
var ct = _cts.Token;
var delayMsec = _settings.loopStepFrequency;
if (delayMsec <= 0) throw new ArgumentOutOfRangeException("loopStepFrequency");
return Task.Run(async () =>
{
while (true)
{
var delayTask = Task.Delay(delayMsec, ct);
action();
await delayTask;
}
}, ct);
}
The responsibility for logging a possible exception/cancellation belongs now to the caller of the method, that (hopefully) awaits the created task.
var task = CreateTask(TheAction);
try
{
await task; // If the caller is async
//task.GetAwaiter().GetResult(); // If the caller is sync
_logger.Info("The task completed successfully");
}
catch (OperationCanceledException)
{
_logger.Info("The task was canceled");
}
catch (Exception ex)
{
_logger.Error("The task failed", ex);
}

How start a Task and define a timeout to finish it

We have a project where we run some tasks in parallel. We are starting Tasks to do this. Every Task runs a code inside a CodeDom (written by customer to customize part of the process). It works fine.
I would like to know how to limitate the time of the execution of a Task. I am afraid of the user write a bad code and it result in something like infinity loop or a bad code which takes a lot of time to perform. I would like to give him 10 seconds to perform its code and after that, kill the thread that is running on the Task and finish the Task.
For sample (pseudo code):
Task[] tasks = GetListOfTasks();
foreach (var task in tasks)
{
task.Start();
}
I tried to do this:
Task.WaitAll(tasks, TimeSpan.FromSeconds(10));
I already have the cancelation token on all these tasks, but the user can't use it.
How can I stop the task execution and kill the tasks if it do not finish on the specified TimeSpan ?
Thank you.
var threadTask = Task.Run(() =>
{
// start thread
thread.Start();
// force to wait the thread
thread.Join();
});
// run a new task to wait the task/thread to finish in a timeout.
// on the continueWith, abort the thread.
Task.Run(() => Task.WaitAll(new[] { threadTask }, TimeSpan.FromSeconds(10)))
.ContinueWith(t =>
{
if (thread.IsAlive)
{
thread.Abort();
}
});

How to get the result of a task when using Task.WhenAny to account for a timeout

I need to add a timeout feature to the task calls in a mobile app. I attempted to complete this by using Task.WhenAny as shown below. This returns the task that finishes first. My problem is that originally I was getting the return value from this task, and I still need it if the task does not get timed out.
Task task = restService.GetRequestAsync(articleAdr, articleParams);
var response = await Task.WhenAny(task, Task.Delay(1000, cts.Token));
response is simply the task that was completed first. How do I get it's result?
I can think of three different possibilities for this scenario.
The first two can be found in Peter Bons' answer.
The third is storing off your two tasks then checking the status after the await Task.WhenAny() is completed.
var workerTask = restService.GetRequestAsync(articleAdr, articleParams);
var cancellationTask = Task.Delay(1000, cts.Token);
await Task.WhenAny(workerTask, cancellationTask);
if (workerTask.Status == TaskStatus.RanToCompletion)
{
// Note that this is NOT a blocking call because the Task ran to completion.
var response = workerTask.Result;
// Do whatever work with the completed result.
}
else
{
// Handle the cancellation.
// NOTE: You do NOT want to call workerTask.Result here. It will be a blocking call and will block until
// your previous method completes, especially since you aren't passing the CancellationToken.
}
One question though, how is your CancellationTokenSource created and initialized and when do you call Cancel on it?
Best would be if your method GetRequestAsync would accept a CancellationToken. Always prefer that if possible since you can create a CancellationTokenSource that initiates a cancel after a set period. Would save you the call to Task.WhenAny.
In general, there are multiple options, one is outlined below:
// Set timeout of 1 second
CancellationTokenSource cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(1));
...
Task task = restService.GetRequestAsync(articleAdr, articleParams);
// Wait until timeout or request is done.
await Task.WhenAny(task, Task.Delay(TimeSpan.FromMilliseconds(-1), cts.Token));
// If the cancellation is not yet requested the request was done before the timeout was reached
if(!cts.IsCancellationRequested)
{
var response = await task;
}
Another options is this:
Task requestTask = restService.GetRequestAsync(articleAdr, articleParams);
var firstCompletedTask = await Task.WhenAny(requestTask, Task.Delay(1000, cts.Token));
if(firstCompletedTask == requestTask)
{
cts.Cancel(); // optionally, will cancel the delay task since it is of no use any more.
var response = await requestTask;
}
A completed task can be awaited as many times as you want and it will always yield the same result.
I think you can take a look to #jamesmontemagno MVVM Helpers. There is an extesion that helps you to add a timeout to a task
MVVM Helpers - Utils
Here you can find a video where James explain how to use it
The-Xamarin-Show-12-MVVM-Helpers
(near 26:38 minutes)
I need to add a timeout feature to the task calls in a mobile app. I attempted to complete this by using Task.WhenAny as shown below.
First, you should be aware that by not passing the CancellationToken to GetRequestAsync, you're not actually cancelling the request. It will continue processing.
Second, I find your code rather odd, since there are two timeouts possible in its current state: the Task.Delay may complete, or the CancellationToken may be signaled. One of these (Task.Delay) is a normal task completion, and the other (CancellationToken) is a true cancellation.
If the CancellationToken is your timeout, then you can use the WaitAsync method from my Nito.AsyncEx.Tasks library:
Task task = restService.GetRequestAsync(articleAdr, articleParams);
await task.WaitAsync(cts.Token);
var result = await task;
If the CancellationToken is a user-requested cancellation, and that the Task.Delay is the timeout you want to apply, then I'd recommend modeling your timeout as another kind of cancellation:
Task task = restService.GetRequestAsync(articleAdr, articleParams);
using (var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cts.Token))
{
timeoutCts.CancelAfter(1000);
await task.WaitAsync(timeoutCts.Token);
}
var result = await task;
If you don't wish to use Nito.AsyncEx.Tasks, then your best option is probably something like this (assuming Task.Delay is intended as a timeout and CancellationToken is a user cancellation request):
Task task = restService.GetRequestAsync(articleAdr, articleParams);
var completed = await Task.WhenAny(task, Task.Delay(1000, cts.Token));
if (completed != task)
throw new OperationCanceledException();
var result = await task;

How to wait for Task to start?

How can I wait for the task to start. The following code fails:
var asyncmethod = ...a Task<TReturn>, with .Start() called on it...;
int waitcounter = 0;
while (!asyncmethod.Wait(1000))
{
waitcounter++;
Log("waiting very long...");
}
ret = asyncmethod.Result;
The asyncmethod.Wait(1000) waits 1 seconds as expected, but the Task is in the state WaitingToRun and will never start running when Wait()ing. On the other hand, when .Result is called, it will start running. How to get it to run without calling .Result?
the Task is in the state WaitingToRun and will never start running when Wait()ing
When a task is in the WaitingToRun state, that means it is ready to start running and is just waiting for its scheduling context to be available, so it can be scheduled and run (as I describe on my blog).
Since the task is still in this state after Wait(1000) finishes, then presumably the task is waiting for the scheduling context that is used by the calling thread, and thus cannot be scheduled until that thread is free.
Task.Result can trigger task inlining and execute task, but apparently Wait() cannot.
Both .Result and .Wait() will permit the task to be inlined, but of course .Wait(x) cannot because it has to honor the timeout.
However, neither .Result nor .Wait() will guarantee inlining - and it's important to keep that in mind when writing reliable code.
the code shouldn't break, regardless of if the task is scheduled on the "current" or a separate thread.
That's an extremely difficult requirement to satisfy. Are you sure you need that?
The easiest solution would be to wait asynchronously:
Task<T> asyncmethod = ...;
int waitcounter = 0;
while (await Task.WhenAny(Task.Delay(1000), asyncmethod) != asyncmethod)
{
waitcounter++;
Log("waiting very long...");
}
ret = await asyncmethod;
Just wait for the task to be completed using:
asyncmethod.Start();
asyncmethod.Wait(); // not needed in most cases
// but if used, the task is completed at this point.
var ret = asyncmethod.Result; // automatically waits for the task to be completed
but basically, the waiting is not neccesary, unless you have a reason for this. From the Task<TResult>.Result-docs:
The get accessor for this property ensures that the asynchronous
operation is complete before returning. Once the result of the
computation is available, it is stored and will be returned
immediately on later calls to Result. (from msdn)
Not really sure why you're doing this, but this can be achieved without blocking the calling thread using Task.IsCompleted and Task.Delay:
public async Task FooAsync()
{
var waitCounter = -1;
var task = Task.Run(() => { });
do
{
waitCounter++;
await Task.Delay(1000);
}
while (!task.IsCompleted)
}
This snippet will call Log a single time if the Task takes more than 1000ms to complete.
private async static void StartTask()
{
Task<object> asyncmethod = ... ;
LogDurationTooLong(asyncmethod, 1000);
var result = await asyncmethod;
}
/// <summary>
/// Logs if a task takes too long to complete.
/// </summary>
/// <param name="asyncmethod">The task to reference.</param>
/// <param name="duration">The duration after which a log entry is made.</param>
private async static void LogDurationTooLong(Task asyncmethod, int duration)
{
Task completedTask = await Task.WhenAny(Task.Delay(duration), asyncmethod);
if (completedTask != asyncmethod)
{
Log("waiting very long...");
}
}

Parallel.ForEach using Thread.Sleep equivalent

So here's the situation: I need to make a call to a web site that starts a search. This search continues for an unknown amount of time, and the only way I know if the search has finished is by periodically querying the website to see if there's a "Download Data" link somewhere on it (it uses some strange ajax call on a javascript timer to check the backend and update the page, I think).
So here's the trick: I have hundreds of items I need to search for, one at a time. So I have some code that looks a little bit like this:
var items = getItems();
Parallel.ForEach(items, item =>
{
startSearch(item);
var finished = isSearchFinished(item);
while(finished == false)
{
finished = isSearchFinished(item); //<--- How do I delay this action 30 Secs?
}
downloadData(item);
}
Now, obviously this isn't the real code, because there could be things that cause isSearchFinished to always be false.
Obvious infinite loop danger aside, how would I correctly keep isSearchFinished() from calling over and over and over, but instead call every, say, 30 seconds or 1 minute?
I know Thread.Sleep() isn't the right solution, and I think the solution might be accomplished by using Threading.Timer() but I'm not very familiar with it, and there are so many threading options that I'm just not sure which to use.
It's quite easy to implement with tasks and async/await, as noted by #KevinS in the comments:
async Task<ItemData> ProcessItemAsync(Item item)
{
while (true)
{
if (await isSearchFinishedAsync(item))
break;
await Task.Delay(30 * 1000);
}
return await downloadDataAsync(item);
}
// ...
var items = getItems();
var tasks = items.Select(i => ProcessItemAsync(i)).ToArray();
await Task.WhenAll(tasks);
var data = tasks.Select(t = > t.Result);
This way, you don't block ThreadPool threads in vain for what is mostly a bunch of I/O-bound network operations. If you're not familiar with async/await, the async-await tag wiki might be a good place to start.
I assume you can convert your synchronous methods isSearchFinished and downloadData to asynchronous versions using something like HttpClient for non-blocking HTTP request and returning a Task<>. If you are unable to do so, you still can simply wrap them with Task.Run, as await Task.Run(() => isSearchFinished(item)) and await Task.Run(() => downloadData(item)). Normally this is not recommended, but as you have hundreds of items, it sill would give you a much better level of concurrency than with Parallel.ForEach in this case, because you won't be blocking pool threads for 30s, thanks to asynchronous Task.Delay.
You can also write a generic function using TaskCompletionSource and Threading.Timer to return a Task that becomes complete once a specified retry function succeeds.
public static Task RetryAsync(Func<bool> retryFunc, TimeSpan retryInterval)
{
return RetryAsync(retryFunc, retryInterval, CancellationToken.None);
}
public static Task RetryAsync(Func<bool> retryFunc, TimeSpan retryInterval, CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource<object>();
cancellationToken.Register(() => tcs.TrySetCanceled());
var timer = new Timer((state) =>
{
var taskCompletionSource = (TaskCompletionSource<object>) state;
try
{
if (retryFunc())
{
taskCompletionSource.TrySetResult(null);
}
}
catch (Exception ex)
{
taskCompletionSource.TrySetException(ex);
}
}, tcs, TimeSpan.FromMilliseconds(0), retryInterval);
// Once the task is complete, dispose of the timer so it doesn't keep firing. Also captures the timer
// in a closure so it does not get disposed.
tcs.Task.ContinueWith(t => timer.Dispose(),
CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default);
return tcs.Task;
}
You can then use RetryAsync like this:
var searchTasks = new List<Task>();
searchTasks.AddRange(items.Select(
downloadItem => RetryAsync( () => isSearchFinished(downloadItem), TimeSpan.FromSeconds(2)) // retry timout
.ContinueWith(t => downloadData(downloadItem),
CancellationToken.None,
TaskContinuationOptions.OnlyOnRanToCompletion,
TaskScheduler.Default)));
await Task.WhenAll(searchTasks.ToArray());
The ContinueWith part specifies what you do once the task has completed successfully. In this case it will run your downloadData method on a thread pool thread because we specified TaskScheduler.Default and the continuation will only execute if the task ran to completion, i.e. it was not canceled and no exception was thrown.

Categories

Resources