1

我正在尝试在我的 MVC 应用程序中生成特定表单的 PDF 副本。由于这很耗时,并且客户端不需要等待这一代发生,我试图将其作为一系列 Fire and Forget Tasks 来触发。

需要注意的一个问题是我需要建立 HttpContext,否则一些我无法更改的底层代码将不起作用。我相信我已经解决了这个问题,但我想把它说出来以防万一。

这是我正在调用的函数...

    private void AsyncPDFFormGeneration(string htmlOutput, string serverRelativePath, string serverURL, string signature, ScannedDocument document, HttpContext httpContext)
    {
        try
        {
            System.Web.HttpContext.Current = httpContext;
            using (StreamWriter stw = new StreamWriter(Server.MapPath(serverRelativePath), false, System.Text.Encoding.Default))
            {
                stw.Write(htmlOutput);
            }

            Doc ABCDoc = new Doc();
            ABCDoc.HtmlOptions.Engine = EngineType.Gecko;
            int DocID = 0;
            DocID = ABCDoc.AddImageUrl(serverURL + serverRelativePath + "?dumb=" + DateTime.Now.Hour.ToString() + DateTime.Now.Minute.ToString() + DateTime.Now.Second + DateTime.Now.Millisecond);
            while (true)
            {
                ABCDoc.FrameRect();
                if (!ABCDoc.Chainable(DocID))
                    break;
                ABCDoc.TextStyle.LeftMargin = 100;
                ABCDoc.Page = ABCDoc.AddPage();
                DocID = ABCDoc.AddImageToChain(DocID);
            }//End while (true...

            for (int i = 1; i <= ABCDoc.PageCount; i++)
            {
                ABCDoc.PageNumber = i;
                ABCDoc.Flatten();
            }

            ScannedDocuments.AddScannedDocument(document, ABCDoc.GetData());

            System.IO.File.Delete(Server.MapPath(serverRelativePath));
        }
        catch (Exception e)
        {
            //Exception is logged to the database, and if that fails, to the Event Log
        }
    }

在其中,我将相关 MVC 表单的 HTML 内容的字符串输出写入 html 文件,将该文件的路径交给 PDF 编写器,生成 PDF,然后删除 html 文件。

我在 Controller POST 方法中调用它,如下所示:

Task.Run(() => AsyncPDFFormGeneration(htmlOutput, serverRelativePath,
    serverURL, signature, document, HttpContext.ApplicationInstance.Context));

该命令被称为 foreach 循环的一部分,该循环构造表单,将它们加载为字符串格式,然后将它们传递给任务。我也试过这个

Task.Factory.StartNew

以防万一Task.Run发生了一些奇怪的事情,但这并没有产生不同的结果。

我遇到的问题是并非所有任务每次都执行。如果我在 Visual Studio 中运行并逐步调试,它每次都能正常工作。但是,当尝试顺序生成 11 个表单时,有时会生成所有表单,有时会生成 3 或 4 个,有时会生成除 1 之外的所有表单。

我将错误日志设置为尽可能广泛,但没有抛出我能找到的异常,并且由于线程中途中止,我的文件结构中没有留下任何生成的 html 文件。

页面从帖子返回的速度与生成的表单数量之间似乎存在轻微的相关性。较长的加载时间通常与生成的更多表单相关......但我的印象应该是无关紧要的。我将它们分离出来,将线程与它们自己的 HttpContext 副本分开,以便随身携带。一旦启动,我认为原始线程不会影响它们。

关于为什么我在某些尝试中只获得 3 个成功的任务,在另一次尝试中全部 11 个,并且没有例外的任何想法?

4

2 回答 2

1
Task.Run(() => AsyncPDFFormGeneration(htmlOutput, serverRelativePath,
serverURL, signature, document, HttpContext.ApplicationInstance.Context));

你在这条线上有一个微妙的竞争条件。问题出在HttpContext.ApplicationInstance.Context物业上。它将在任务开始时进行评估。如果它发生在请求结束之前,这很好。但是如果由于某种原因任务需要一些时间来启动,那么请求将首先完成,并且HttpContext将为空。因此,您将有一个空引用异常,给您一种任务没有启动的印象(实际上,它确实启动了,但在您的 try/catch 之外立即崩溃了)。

为避免这种情况,只需将上下文存储在局部变量中,并将其用于Task.Run

var context = HttpContext; // Or HttpContext.ApplicationInstance.Context, but I don't really see the point
Task.Run(() => AsyncPDFFormGeneration(htmlOutput, serverRelativePath, serverURL, signature, document, context));

也就是说,我不知道您使用的是什么 API 需要System.Web.HttpContext.Current设置,但对于即发即弃的任务来说,这似乎是一个非常糟糕的选择。即使您在本地保存 HttpContext,它仍然会被清理,所以我不确定它是否会按预期运行。

此外,正如评论中提到的,在 ASP.NET 上启动即发即弃的任务是危险的。你应该HostingEnvironment.QueueBackgroundWorkItem改用。

于 2017-06-10T00:05:46.790 回答
0

我会尝试使用await Task.WhenAll(task1, task2, task3, etc),因为您的应用程序可能会在所有任务完成之前关闭。

于 2017-06-09T22:19:54.100 回答