Reputation: 23
I need to get some data from external service and store them in cache but it takes some time. Sometimes the request is quite long.
I'd like to throw TimeoutException and inform user that it is required to wait couple of seconds and retry the request but method which is responsible for collecting data should continue it's work in background.
Let's assume that I have a method:
public Task<CustomEntity> GetCustomEntity()
{
// time consuming task e.g call to external service
return result;
}
Next:
public async Task<CustomEntity> SomeMethod()
{
return await GetCustomEntity();
}
I'd like to somehow measure execution time of GetCustomEntity()
and after defined time throw TimeoutException but I'd like to continue Method execution in background.
I tried to use Task.WaitAny
with Timeout parameter and to write some wrapper with cancellationToken but all of such solutions stop task execution in background.
Kind of solution is to run task again after timeout is achieved. e.g:
// Wrapper
var taskToRun = Task.Factory.StartNew(() => GetCustomEntity());
if (Task.WaitAny(new Task [] { taskToRun }, TimeSpan.FromMilliseconds(timeout)) < 0)
{
Task.WhenAll(taskToRun);
throw new TimeOutException();
}
But solution above is a bit silly. In described situation, there is another question. How to return value from wrapper if request fit in timeout.
Upvotes: 2
Views: 1304
Reputation: 3499
When looking at your wrapper solution you wait on the task to complete with a timeout. First you do not need WaitAny
here. You can basically use Task.Wait(timeout)
. Secondly Task.WhenAll
is not needed for the task to continue in the background so this should be sufficent:
var taskToRun = Task.Factory.StartNew(() => GetCustomEntity());
if (!taskToRun.Wait(TimeSpan.FromMilliseconds(timeout)))
{
throw new TimeoutException();
// taskToRun will continue its execution in the background
}
Keep in mind that .Wait
or .WaitAny
will block the current thread from execution and could lead to race conditions! If you want to use async/await
to avoit that you could use Task.WhenAny
:
var taskToRun = Task.Factory.StartNew(() => GetCustomEntity());
var completedTask = await Task.WhenAny(taskToRun, Task.Delay(timeout));
if (completedTask == taskToRun)
{
// No Timeout
}
else
{
// Timeout
throw new TimeoutException();
}
if you want to return the value if the request finished in time you could then do s.th. like this:
var taskToRun = Task.Factory.StartNew(() => GetCustomEntity());
var completedTask = await Task.WhenAny(taskToRun, Task.Delay(timeout));
if (completedTask == taskToRun)
{
// No Timeout
return await completedTask;
}
else
{
// Timeout
throw new TimeoutException();
}
or with the wait approach:
var taskToRun = Task.Factory.StartNew(() => GetCustomEntity());
if (!taskToRun.Wait(TimeSpan.FromMilliseconds(timeout)))
{
throw new TimeoutException();
// taskToRun will continue its execution in the background
}
else
{
return taskToRun.Result;
}
Upvotes: 1