Calling Func from a task

I am writing a dedicated reporting tool that allows users to create very broad queries. I wanted to add a timeout to this, so if the user creates something that will run for quite a long time, the whole system won't stop. 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 good, but I wanted to refactor it to something like this using Func so that I can pass either 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 the return type of 'Func', whereas I just want task.Result to return my list>. Are there any ways to fix this?

+3


source to share


2 answers


In your method, RunReport

you are not actually calling method

Func. You are reconfiguring a delegate method

. Therefore it Task.Result

is displayed as Func<>

.

var task = Task.Factory.StartNew(() => method, token);

      

The above code is

var task = Task.Factory.StartNew(() =>
                                 {
                                    return method;
                                 }, token);

      

To execute it, you need to call it with method call syntax.

var task = Task.Factory.StartNew(() => method(report), token);

      

To do this, you need a report as a 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

public List<List<SimpleDisplayField>> FindReport(int reportId)
{
    var report = GetReportById(reportId);
    return (report.BidType == "LOB")
           ? RunReport(FindLOBReport, report)
           : RunReport(FindFWOReport, report);
}

      

It's also worth noting that yours is Task

not canceled. It will continue to work. This is important to note. If your method FindLOBReport

is essentially calling the database, and if it takes time, you are better off using a property SqlCommand.Timeout

. This will undo the underlying operation.

Regarding @YuvalItzchakov's comment. He is not talking about waiting for the start of the task and waiting for it to complete synchronously. You must take a serious look at pending .

Btw 20,000 milliseconds not 2 seconds. It's 20 seconds.

+3


source


Thanks for the feedback everyone. Now my solution 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 use this to timeout the request:

 using (TRACSEntities db = new TRACSEntities())
            {
                db.Database.CommandTimeout = 60;
                var query = // and so on
            }

      

0


source







All Articles