从任务中调用函数

Calling a Func From a Task

我正在编写一个自定义报告工具,允许用户创建一些非常广泛的查询。我想为此添加一个超时,这样如果用户创建的内容最终会 运行 很长,整个系统就不会停止。我想到了这个:

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;
        }
    }

这很好,但我想将它重构为类似这样的东西,使用 Func,这样我就可以将 FindLOBReport 或 FindFWOReport 作为参数传递:

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;
    }

然而,task.Result 是 'Func' return 类型,而我只想 task.Result 到 return 我的列表>。有什么办法可以解决这个问题吗?

在您的 RunReport 方法中,您实际上并没有调用 method Func。您正在重新调整 method 代表。这就是为什么 Task.Result 被推断为 Func<>.

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

以上代码等于

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

要执行它,您需要使用方法调用语法调用它。

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

要做到这一点,您需要将报告作为参数。

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;
}

那么你可以称它为

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

还值得注意的是,您的 Task 并未取消。它将继续 运行。这是一件需要注意的重要事情。如果您的 FindLOBReport 方法本质上是调用数据库,如果那是需要时间的——您最好使用 SqlCommand.Timeout 属性。这将取消基础操作。

尊重@YuvalItzchakov 的评论。他说等待任务开始并同步完成是没有意义的。你应该认真看看awaiting it.

顺便说一句,20000 毫秒不是 2 秒。是20秒。

感谢大家的反馈。我的解决方案现在看起来像这样:

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;
    }

在 FindLOB/FWOReport 中,我使用它来使查询超时:

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