Reputation: 559
I am writing a custom reporting tool that allows the users to possibly create some very broad queries. I wanted to add a timeout to this so if the user creates something that will end up running quite long, the entire system won't stall. I came up with this:
public List<List<SimpleDisplayField>> FindReport(int reportId)
{
var report = GetReportById(reportId);
var tokenSource = new CancellationTokenSource();
CancellationToken token = tokenSource.Token;
int timeOut = 20000; // 2 seconds
if (report.BidType == "LOB")
{
var task = Task.Factory.StartNew(() => FindLOBReport(report), token);
if (!task.Wait(timeOut, token))
{
throw new Exception("Report ran for more than 10 seconds.. run again or add more filters.");
}
return task.Result;
}
else
{
var task = Task.Factory.StartNew(() => FindFWOReport(report), token);
if (!task.Wait(timeOut, token))
{
throw new Exception("Report ran for more than 10 seconds.. run again or add more filters.");
}
return task.Result;
}
}
Which is fine but I wanted to refactor it into something like this instead, using Func's so I could pass either the FindLOBReport or FindFWOReport as a parameter:
public List<List<SimpleDisplayField>> FindReport(int reportId)
{
var report = GetReportById(reportId);
if (report.BidType == "LOB")
{
return RunReport(FindLOBReport(report));
}
else
{
return RunReport(FindFWOReport(report));
}
}
private List<List<SimpleDisplayField>> RunReport(Func<CustomReport, List<List<SimpleDisplayField>>> method)
{
var tokenSource = new CancellationTokenSource();
CancellationToken token = tokenSource.Token;
int timeOut = 20000; // 2 seconds
var task = Task.Factory.StartNew(() => method, token);
if (!task.Wait(timeOut, token))
{
throw new Exception("Report ran for more than 10 seconds.. run again or add more filters.");
}
return task.Result;
}
However, task.Result is a 'Func' return type, whereas I just want task.Result to return my List>. Are there any ways to remedy this?
Upvotes: 3
Views: 414
Reputation: 559
Thank you for the feedback everyone. My solution now looks like this:
public List<List<SimpleDisplayField>> FindReport(int reportId)
{
var report = GetReportById(reportId);
return (report.BidType == "LOB")
? RunReportAsync(FindLOBReport, report).Result
: RunReportAsync(FindFWOReport, report).Result;
}
private async Task<List<List<SimpleDisplayField>>> RunReportAsync(Func<CustomReport, List<List<SimpleDisplayField>>> method, CustomReport report)
{
var task = await Task.Factory.StartNew(() => method.DynamicInvoke(report));
return (List<List<SimpleDisplayField>>)task;
}
And in FindLOB/FWOReport I am using this to timeout the query:
using (TRACSEntities db = new TRACSEntities())
{
db.Database.CommandTimeout = 60;
var query = // and so on
}
Upvotes: 0
Reputation: 73452
In your RunReport
method, you're not actually calling the method
Func. You're retuning the method
delegate. That is why the Task.Result
is inferred as Func<>
.
var task = Task.Factory.StartNew(() => method, token);
Above code is equal to
var task = Task.Factory.StartNew(() =>
{
return method;
}, token);
To execute it, you need to invoke it using method call syntax.
var task = Task.Factory.StartNew(() => method(report), token);
To be able to do that, you need the report as parameter.
private List<List<SimpleDisplayField>> RunReport(Func<CustomReport, List<List<SimpleDisplayField>>> method,CustomReport report)
{
var tokenSource = new CancellationTokenSource();
CancellationToken token = tokenSource.Token;
int timeOut = 20000; // 2 seconds
var task = Task.Factory.StartNew(() => method(report), token);
if (!task.Wait(timeOut, token))
{
throw new Exception("Report ran for more than 10 seconds.. run again or add more filters.");
}
return task.Result;
}
Then you can call it as
public List<List<SimpleDisplayField>> FindReport(int reportId)
{
var report = GetReportById(reportId);
return (report.BidType == "LOB")
? RunReport(FindLOBReport, report)
: RunReport(FindFWOReport, report);
}
Also worth noting that your Task
isn't cancelled. It will continue to run. It is a important thing to note. If your FindLOBReport
method essentially calls Database and if that's what taking time --you're better off using SqlCommand.Timeout
property. That will cancel the underlying operation.
Respecting the @YuvalItzchakov's comment. He says no point in waiting for the starting the Task and waiting for its completion synchronously. You should seriously look at awaiting it.
Btw 20000 milliseconds is not 2 seconds. It is 20 seconds.
Upvotes: 3