I have to execute a long process operation in a thread and continue by returning the result to a function. Here is my code :
Task<ProductEventArgs>.Factory.StartNew(() =>
{
try
{
// long operation which return new ProductEventArgs with a list of product
}
catch (Exception e)
{
return new ProductEventArgs() { E = e };
}
}).ContinueWith((x) => handleResult(x.Result), TaskScheduler.FromCurrentSynchronizationContext());
The problem is actually I don't have a timeout. I want to put a timer in order to return something like this :
new ProductEventArgs() { E = new Exception("timeout") };
if the timeout is reached.
Can't use await/async.
Thanks a lot !
This code does what you have expressed here:
var timeout = TimeSpan.FromSeconds(5);
var actualTask = new Task<ProductEventArgs>(() =>
{
var longRunningTask = new Task<ProductEventArgs>(() =>
{
try
{
Thread.Sleep(TimeSpan.FromSeconds(10)); // simulates the long running computation
return new ProductEventArgs();
}
catch (Exception e)
{
return new ProductEventArgs() { E = e };
}
}, TaskCreationOptions.LongRunning);
longRunningTask.Start();
if (longRunningTask.Wait(timeout)) return longRunningTask.Result;
return new ProductEventArgs() { E = new Exception("timed out") };
});
actualTask.Start();
actualTask.Wait();
Console.WriteLine("{0}", actualTask.Result.E); // handling E
As you see longRunningTask
is created with TaskCreationOptions.LongRunning
option. That way it will have a dedicated Thread
for it's execution and does not interfere with normal behavior of ThreadPool
by occupying a thread from there for too long - which will be needed for other thing like i.e. UI. That's important for long running tasks.
Note: You could then handle actualTask
with ContinueWith
but I wanted to express the essence here.
You should use CancellationToken
s:
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
var token = cts.Token;
Task<ProductEventArgs>.Factory.StartNew(() =>
{
try
{
// occasionally, execute this line:
token.ThrowIfCancellationRequested();
}
catch (OperationCanceledException)
{
return new ProductEventArgs() { E = new Exception("timeout") };
}
catch (Exception e)
{
return new ProductEventArgs() { E = e };
}
}).ContinueWith((x) => handleResult(x.Result), TaskScheduler.FromCurrentSynchronizationContext());
You may use returned task object for StartNew method and then user Wait method to determine timeout.
Task<ProductEventArgs> task = Task<ProductEventArgs>.Factory.StartNew(() => {...});
if (!Task.Wait(new TimeSpan(0,0,1,0)) // wait for 1 minute
{
// throw exception or something else if timeout
}
You can run a Task.Delay(timeout)
task in parallel and check what task was first to complete (Task.WhenAny()
is very handy in this case):
public void FetchProduct(TimeSpan timeout)
{
var fetchTask = Task<ProductEventArgs>.Factory.StartNew(
() =>
{
try
{
// long operation which return new ProductEventArgs with a list of product
}
catch(Exception e)
{
return new ProductEventArgs() { E = e };
}
});
Task<ProductEventArgs> resultTask;
if(timeout != Timeout.InfiniteTimeSpan)
{
var timeoutTask = Task.Delay(timeout);
resultTask = Task.WhenAny(resultTask, timeoutTask).ContinueWith<ProductEventArgs>(
t =>
{
// completed task is the result of WhenAny
if(t.Result == fetchTask)
{
return fetchTask.Result;
}
else
{
return new ProductEventArgs() { E = new TimeoutException() };
}
});
}
else
{
resultTask = fetchTask;
}
resultTask.ContinueWith(x => handleResult(x.Result), TaskScheduler.FromCurrentSynchronizationContext());
}
Note that this solution doesn't have any cancellation logic, and your long running task will be still running even if it times out.
Just start another task within the main task (surrogate):
Task.Factory.StartNew(() =>
{
// returns a string result
var tsk = new Task<string>(() => { return VeryImportantThingsToDo(); });
try
{
tsk.Start();
if (!tsk.Wait(5000))
throw new TimeoutException();
return tsk.Result;
}
catch (TimeoutException)
{
// Jabba Dabba Doooooooohhhhhh
}
return "<unknown>";
}).ContinueWith((o) => string result = o.Result));