从MVC Controller启动时,Task.Run偶尔会无声地失败

时间:2017-06-09 22:02:17

标签: c# asp.net-mvc multithreading asynchronous fire-and-forget

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

需要注意的一点是,我需要建立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内容的String输出写入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个在另一次尝试中,并且没有例外的任何想法?

2 个答案:

答案 0 :(得分:0)

我会尝试使用[CustomAuth] public class myHub : Hub //Or some other name ,因为您的应用程序可能会在所有任务完成之前关闭。

答案 1 :(得分:0)

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代替。