logeyg
logeyg

Reputation: 559

Calling a Func From a Task

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

Answers (2)

logeyg
logeyg

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

Sriram Sakthivel
Sriram Sakthivel

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

Related Questions