【问题标题】:Problems getting newly created thread to send outputs to asp:panel in ASP.NET C#在 ASP.NET C# 中让新创建的线程将输出发送到 asp:panel 时出现问题
【发布时间】:2012-07-06 19:11:20
【问题描述】:

我正在创建一个用于 Intranet 的文件处理器。 我在另一个问题中描述了它 - ERR_EMPTY_RESPONSE when processing a large number of files in ASP.Net using C#

现在,正如上述问题的答案所建议的,我正在尝试使用线程来执行文件处理任务。

但是有一个问题。我需要新创建的线程将反馈写入页面中的组件(asp:panel,或 div,或其他)。这些反馈将来自多个数据库操作。

应用程序读取这些 txt,解释其中的每一行,并将数据插入数据库。插入数据库的每一行都必须返回一个反馈,例如“注册表 'regname' 插入成功”,或“我在文件 'filename' 中插入注册表 'regname' 时遇到问题,跳到下一个注册表”。

我做了一些非常简单的测试:

protected void DoImport()
{
    try
    {
        MainBody.Style.Add(HtmlTextWriterStyle.Cursor, "wait");

        int x = 0;
        while (x < 10000)
        {
            ReturnMessage(String.Format("Number {0}<hr />", x), ref pnlConfirms);
            x++;
        }
    }
    catch (Exception ex)
    {
        ReturnMessage(String.Format("<font style='color:red;'><b>FATAL ERROR DURING DATA IMPORT</b></font><br /><br /><font style='color:black;'><b>Message:</b></font><font style='color:orange;'> {0}</font><br />{1}", ex.Message, ex.StackTrace), ref pnlErrors);
    }
    finally
    {
        MainBody.Style.Add(HtmlTextWriterStyle.Cursor, "default");
    }
}

这个函数是从 Page_Load 调用的,它用一行数字填充一个名为“pnlConfirms”的 asp:panel,但在加载时是一次性的。

我改成:

protected void DoImport()
{
    try
    {
        MainBody.Style.Add(HtmlTextWriterStyle.Cursor, "wait");
        ThreadPool.QueueUserWorkItem(new WaitCallback(DoWork));
    }
    catch (Exception ex)
    {
        ReturnMessage(String.Format("<font style='color:red;'><b>FATAL ERROR DURING DATA IMPORT</b></font><br /><br /><font style='color:black;'><b>Message:</b></font><font style='color:orange;'> {0}</font><br />{1}", ex.Message, ex.StackTrace), ref pnlErrors);
    }
    finally
    {
        MainBody.Style.Add(HtmlTextWriterStyle.Cursor, "default");
    }
}

private void DoWork(Object stateInfo)
{
    int x = 0;
    while (x < 10000)
    {
        ReturnMessage(String.Format("Number {0}<hr />", x), ref pnlConfirms);
        x++;
    }
}

两者都使用这个功能:

public void ReturnMessage(string message, ref Panel panel, bool reset = false)
{
    if (reset)
    {
        panel.Controls.Clear();
    }
    Label msg = new Label();
    msg.Attributes.Add("width", "100%");
    msg.Text = message;
    panel.Controls.Add(msg);
}

我需要ThreadPool.QueueUserWorkItem(new WaitCallback(DoWork)); 用反馈填充这些 asp:panels - 例如插入错误和警告。

我的代码在try...catch 语句下已经有这些反馈,但它们没有从线程池中获取任何asp:panel 的输出(当直接从DoImport() 函数调用时,它可以工作,就像我发布的第一个示例一样)。

我做错了什么,但我不知道是什么(我研究了将近 2 周)。请帮忙!

【问题讨论】:

    标签: c# multithreading


    【解决方案1】:

    在 ASP.NET 中,当浏览器请求页面时,该页面会在其处理完成后立即呈现并发送到浏览器,因此浏览器将在页面最终呈现时显示该页面。

    根据您的代码,您尝试呈现页面,显示等待光标,并期望它显示在浏览器上,然后光标被默认光标更改。正如我所解释的,与使用或不使用其他线程无关,页面在完全呈现之前不会被发送到浏览器。所以你永远不会在客户端看到等待光标。

    获得您想要做的最简单的等待是使用 Web 服务(传统的 .asmx 或 WCF)和 AJAX(jquery os ASP.NET AJAX)。

    1) 创建一个进行处理的 Web 服务

    2) 创建一个发送到浏览器的页面,并使用 javascript(jQuery 或 ASP.NET AJAX)调用 Web 服务,并显示一些内容让用户知道正在处理请求。 (等待光标,或者更好的动画 gif)

    3) 当流程完成时,您的 javascript 将从网络服务获取响应,您可以更新页面让用户知道流程已完成。

    如果您没有 javascript 方面的经验,您可以使用以下方法完成大部分任务:

    • ScriptManager 可用于您的客户端 (other interesting article) 的 create a javascript web service proxy,并且是其余控件所必需的

    • 一些 javascript(或 jquery)可用于更新客户端的“进程运行/进程完成提示”。 IE。当对 Web 服务的调用结束时,您可以使用 javascript 使用 DOM 更新页面,或者加载新页面或带有特殊参数的同一页面以显示处理结果

    这样你就可以为所欲为:

    1) 以显示进程正在运行的状态显示页面

    2) 以显示进程结束的状态显示相同或其他页面

    诀窍在于将浏览器与服务器通信,而这只能使用一些可用的 ajax 技术来完成。

    另一种典型的技术是使用 jQuery.ajax,比如explained in encosia.com

    根据 OP 消息,所有文件的处理过程会非常缓慢,以至于会中断 Web 服务调用。如果是这种情况,您可以使用以下解决方案:

    1) 创建一个处理一个(或一批)待处理文件的 Web 服务,并在处理完当前文件(或一批)时至少返回待处理文件的数量。

    2) 从客户端 (javascript) 调用 Web 服务。完成后,更新显示待处理文件数量的页面,如果此数量大于零,请再次调用 Web 服务。

    3) 当调用web服务返回0个挂起的文件时,可以更新页面显示工作已经完成,不要再调用了。

    如果一次处理所有文件,客户端不会有任何反馈,也会出现超时。此外,IIS 可以决定停止正在工作的工作线程。 IIS 这样做有几个原因。

    一个更可靠但更难实施的解决方案是:

    1) 实现一个 Windows 服务,用于处理文件

    2) 实现一个返回待处理文件数量的 Web 服务(您可以使用文件系统、数据库表或类似的东西间接地与 Windows 服务和 Web 应用通信)

    3) 使用网页中的计时器(ajax 计时器或 javascript setInterval)使用网络服务每 N 秒轮询一次服务器,直到待处理文件的数量为 0。

    更难的方法是在 Windows 服务中托管 WCF 服务,而不是在 Web 应用和 Windows 服务之间进行间接通信。这种情况要复杂得多,因为您需要使用线程来完成工作,并参加对 wcf 服务的调用。如果您可以使用间接通信,则实施起来会容易得多。 dtabse 表是一种简单而有效的解决方案:您的工作进程在处理文件时会更新表中的一行,并且 Web 服务会从该表中读取进度状态。

    对于一个不那么简单的问题,有很多不同的想法。

    【讨论】:

    • 是的。 Ajax 可能是这样做的方法。我正在研究通过 JQuery 来做这件事。我在 JS 方面很有经验,只需要让它在 ASP.Net 上运行。我想要达到的效果是让 asp:panel 在运行时用该线程发送的反馈动态填充...这可能吗?
    • 好吧..我将澄清主要过程:该页面显示了一个文件夹列表,其中包含 txt 文件。用户通过标记每个文件夹的复选框来选择他想要处理的文件夹,然后单击“处理”按钮。然后,页面使用参数调用自身,Page_Load 事件检查这些参数。然后,它创建一个文件夹数组(在检查它们之后),并通过循环下的循环,它处理那里的所有文件。它打开每个文件并逐行读取。并将其分成字符组以输入到数据库中的表中。每一行都作为错误、警告或成功返回。
    • 难道没有办法在处理的同时将这些返回的消息实时放入 div 或面板中?!即使使用ajax也无法做到吗?通过间接通信(通过使用计时器)创建外部服务太不精确了,我看不到获取这些消息的方法......必须有另一种方法:/
    • 如果您的网络浏览器可以收听通知,那将是可能的。但是浏览器只能询问并得到响应(即 AJAX)。这就是为什么我建议轮询或处理批次。每次处理文件时收到通知是否如此重要?考虑到通过网络获取信息既昂贵又不安全:您可能会得到无序的信息,甚至会丢失它。间接沟通不是坏事。您可以在表中为要处理的每个文件创建一行,并在处理文件时对其进行更新。您的 Web 服务可以从中获取所有信息并将其发送到浏览器:文件名或 whatevr
    • 获得通知至关重要。但好吧,我放弃了。我将创建一个 Windows 应用程序来完成导入工作。感谢您的宝贵时间!
    【解决方案2】:

    您正在启动新线程(或者更准确地说,在线程池中的一个空闲线程上运行您的代码)而不是在主线程中等待结果。如果您想走这条路,则需要使用 Thread.Join(如果您将使用手动创建线程)或其他同步机制作为事件。

    您链接到的问题建议使用您没有使用的异步页面。您将开始处理请求,启动任务并释放线程,当任务完成时您完成请求。

    旁注:考虑简单地在处理请求的主线程上进行所有转换。除非您期望缓慢的 I/O 完成将 CPU 工作从一个线程转移到另一个线程的任务,否则可能不会产生显着的收益。请测量您当前解决方案的性能,并确认它不符合您为应用程序设置的性能目标。 (如果您出于娱乐/教育目的这样做,则不适用)。

    【讨论】:

    • 嗯.. 转换是一项非常繁重的工作,正如我在另一个问题上所说...处理 10.530 文件只是挂起主线程。
    • Web 服务器中长时间运行的任务通常是个坏主意。但是您应该能够将您的主请求转换为异步处理程序并为每个文件发送更新......请注意,您必须期待结果的部分更新,因为在处理完所有文件之前您不会完成请求。或者更好的是重新设计您的转换框架,以使用一些外部进程或可重新启动的排序任务并从网页轮询状态。
    猜你喜欢
    • 2011-05-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-21
    • 2022-11-21
    • 2011-03-20
    • 1970-01-01
    相关资源
    最近更新 更多