• ASP.NET中多线程问题


    以前在使用VB来实现多线程的时候,发现有一定的难度。虽然也有这样那样的方法,但都不尽人意,但在C#中,要编写多线程应用程序却相当的简单。这篇文章将作简要的介绍,以起到抛砖引玉的作用!
        .NET将关于多线程的功能定义在System.Threading名字空间中。因此,要使用多线程,必须先声明引用此名字空间(using System.Threading;)。
           即使你没有编写多线程应用程序的经验,也可能听说过“启动线程”“杀死线程”这些词,其实除了这两个外,涉及多线程方面的还有诸如“暂停线程”“优先级”“挂起线程”“恢复线程”等等。下面将一个一个的解释。
        a.启动线程
        顾名思义,“启动线程”就是新建并启动一个线程的意思,如下代码可实现:
        Thread thread1 = new Thread(new ThreadStart( Count));
        其中的 Count 是将要被新线程执行的函数。
        b.杀死线程
        “杀死线程”就是将一线程斩草除根,为了不白费力气,在杀死一个线程前最好先判断它是否还活着(通过 IsAlive 属性),然后就可以调用 Abort 方法来杀死此线程。
        c.暂停线程
        它的意思就是让一个正在运行的线程休眠一段时间。如 thread.Sleep(1000); 就是让线程休眠1秒钟。
        d.优先级
        这个用不着解释了。Thread类中幸桓鯰hreadPriority属性,它用来设置优先级,但不能保证操作系统会接受该优先级。一个线程的优先级可分为5种:Normal, AboveNormal, BelowNormal, Highest, Lowest。具体实现例子如下:
        thread.Priority = ThreadPriority.Highest;
        e.挂起线程
        Thread类的Suspend方法用来挂起线程,知道调用Resume,此线程才可以继续执行。如果线程已经挂起,那就不会起作用。
        if (thread.ThreadState = ThreadState.Running)
        {
             thread.Suspend();
        }
        f.恢复线程
        用来恢复已经挂起的线程,以让它继续执行,如果线程没挂起,也不会起作用。
        if (thread.ThreadState = ThreadState.Suspended)
        {
             thread.Resume();
        }
        下面将列出一个例子,以说明简单的线程处理功能。此例子来自于帮助文档。
        using System;
        using System.Threading;
        // Simple threading scenario: Start a static method running
        // on a second thread.
        public class ThreadExample {
            // The ThreadProc method is called when the thread starts.
            // It loops ten times, writing to the console and yielding
            // the rest of its time slice each time, and then ends.
            public static void ThreadProc() {
                for (int i = 0; i < 10; i++) {
                    Console.WriteLine("ThreadProc: {0}", i);
                    // Yield the rest of the time slice.
                    Thread.Sleep(0);
                }
            }
       
            public static void Main() {
                Console.WriteLine("Main thread: Start a second thread.");
                // The constructor for the Thread class requires a ThreadStart
                // delegate that represents the method to be executed on the
                // thread. C# simplifies the creation of this delegate.
                Thread t = new Thread(new ThreadStart(ThreadProc));
                // Start ThreadProc. On a uniprocessor, the thread does not get
                // any processor time until the main thread yields. Uncomment
                // the Thread.Sleep that follows t.Start() to see the difference.
                t.Start();
                //Thread.Sleep(0);
       
                for (int i = 0; i < 4; i++) {
                    Console.WriteLine("Main thread: Do some work.");
                    Thread.Sleep(0);
                }
       
                Console.WriteLine("Main thread: Call Join(), to wait until ThreadProc ends.");
                t.Join();
                Console.WriteLine("Main thread: ThreadProc.Join has returned. Press Enter to end program.");
                Console.ReadLine();
            }
        }

    源文档 <http://www.haoxiai.net/wangzhanzhizuo/aspnet/54093.html>


    前几天遇到了一个问题,我在页面逻辑里需要调用一个webservice,处理一个比较耗时的操作,但是我不需要知道其返回值。于是我希望ASP.NET能像winform一样使用自动生成的webservice异步方法,通过ASP.NET多线程来解决这一问题。
    你是不是想说:在页面调用webservice的时候,直接调用其异步实现不就完了吗?
    这其实是行不通的,为了实现异步调用,我们需要对页面进行小小的改动,在Page元素里加上Async=true
    我们很快就会发现这样做的问题:
    让我们测试一下吧,现在我们在一个webservice的Helloworld方法中放入一个Thread。Sleep(10000),然后调用他的异步实现。通过调试,我们可以发现虽然程序运行至HelloworldAsync时,非常快速的返回并往下运行,但是当所有逻辑处理完成后,页面并不Response,而是硬生生等待我们的线程睡醒了才返回。
    可是如果我希望真正做到调了不管怎么办呢?

    你可以使用ASP.NET多线程中的Thread,或者ThreadPool,自己来启动一个线程,我推荐使用ThreadPool,这样的话,这些线程都会被iis的线程池管理起来,不会造成崩溃
    我们来分析一下ASP.NET多线程这两种模式的运用有什么特点
    WebService自带的异步模式为下图的模式

    这种模式适合无返回的情况,这种情况下,对子线程的调用应该越晚越好,我们可以看到,主、子线程共存的时间越短,我们的稀缺资源线程就越安全,请注意的是,也许总的执行时间不会比同步的情况更少,但是我们很快就返回了用户界面,所以用户体验能够得到提高
    使用web多线程的缺点 :
    看了上面的叙述,你也许会说,那干脆把我所有的调用都改成异步调用吧,你尽管去做吧,绝对是一场灾难,因为在异步的同时,一定一会产生一个新的线程等待调用的返回,即使你调用函数的返回值为void,所以异步调用的负面效果将是会产生许多子线程,所以注意当你的调用非常耗时,这个子线程也将长期占用你的线程池,如果这样的调用大量出现,照样会消耗掉所有的可用线程
    那么什么情况下适合在web上使用哪种ASP.NET多线程模式呢
    我们来看看这段伪代码,他的用途是提交一个报告,方法传入一个报告,并从一个WebService中获得一些报告的内容,接着插入数据库,然后在文件服务器上生成一个报告文件,最后发出一个通知,让我们逐条命令的过一下这个方法,看看什么地方适合改为异步调用?(记得我们的讨论都是基于web的,关于桌面运用的多线程请参考 多线程总结一)

    1. public void CreateReport(Report report){
    2.
    3. //从webservice上取得报告的一些信息,
    4. 不取得这些信息报告,报告是不完整的,是不能提交的
    5.
    6. Report fullreport=CallWebService(report);
    7.
    8. //插入数据库,很重要的工作
    9.
    10. InsertIntoDataBase(fullreport)
    11.
    12. try{
    13.
    14. //生成报告文件,这里是一个耗时而且容易出错的操作
    15.
    16. WriteStaticFile(fullreport)
    17.
    18. }
    19.
    20. catch{//记录错误日志。。。。}
    21.
    22. //这个只是通知邮件
    23.
    24. CallMailService2(fullreport)
    25.
    26. }
    第一条语句CallWebService()从一个webservice里加载一些报告的内容,这个是业务逻辑相关的,因为如果不加载的话报告内容是不完整的,不能提交,显然不能改为异步调了不管的模式,在这里你可以尝试模式一,但是这个改动是没有作用的,因为其他所有的过程,包括插入数据库,生成报告都依赖于这个方法的返回,所以如果我们在这里使用异步的话,其他的所有操作都必须等待他的返回,所以采用异步除了多增加了线程以外,一点时间也不能节省
    再来看插入数据库,和上面一样也没有必要使用异步调用
    生成报告这里比较有趣,确实他是一个和逻辑息息相关的操作,但是通过分析代码,我们可以看出,虽然报告生成是一个重要业务步骤,但是并没有严格到说"如果不能生成报告,就必须回滚上面的操作",并且如果操作失败,在catch中也仅仅是记录了日志,并没有需要尝试重写的逻辑,(很有可能另外的某个程序或者某人,会定时查看日志,发现有错误就重新生成文件)也就是说,就这段代码而言,生成也可以算一个额外逻辑,那么自然也可以去异步操作.可是:千万注意!!
    由于生成报告需要的时间较长,那么生成报告的子线程会长时间运行,长期无法返回线程池,如果请求量太大,频率太快,那就会耗尽线程资源了.
    平心而论,这个问题其实不是异步造成的,即使时同步调用,执行此操作也需要化肥很长时间,调用量太大,频率太快,也会造成排队.而且由于返回时间太长,用户体验也不会好,所以我们的这个ASP.NET多线程的改造应该是有益的。

    源文档 <http://developer.51cto.com/art/200907/138426.htm>


    在.NET中编写的程序将被自动的分配一个线程.让我们来看看用C#编程语言创建线程并且继续学习线程的知识。我们都知道.NET的运行时环境的主线程由Main ()方法来启动应用程序,而且.NET的编译语言有自动的垃圾收集功能,这个垃圾收集发生在另外一个线程里面,所有的这些都是后台发生的,让我们无法感觉到发生了什么事情.在这里默认的是只有一个线程来完成所有的程序任务,但是正如我们在第一篇文章讨论过的一样,有可能我们根据需要自己添加更多的线程让程序更好的协调工作。比如说我们的例子中,一个有用户输入的同时需要绘制图形或者完成大量的运算的程序,我们必须得增加一个线程,让用户的输入能够得到及时的响应,因为输入对时间和响应的要求是紧迫的,而另外一个线程负责图形绘制或者大量的运算。

    .NET 基础类库的System.Threading命名空间提供了大量的类和接口支持多线程。这个命名空间有很多的类,我们将在这里着重讨论Thread这个类。

    System.Threading.Thread类是创建并控制线程,设置其优先级并获取其状态最为常用的类。他有很多的方法,在这里我们将就比较常用和重要的方法做一下介绍:

    Thread.Start():启动线程的执行;

    Thread.Suspend():挂起线程,或者如果线程已挂起,则不起作用;

    Thread.Resume():继续已挂起的线程;

    Thread.Interrupt():中止处于 Wait或者Sleep或者Join 线程状态的线程;

    Thread.Join():阻塞调用线程,直到某个线程终止时为止

    Thread.Sleep():将当前线程阻塞指定的毫秒数;

    Thread.Abort():以开始终止此线程的过程。如果线程已经在终止,则不能通过Thread.Start()来启动线程。

    通过调用Thread.Sleep,Thread.Suspend或者Thread.Join可以暂停/阻塞线程。调用Sleep()和Suspend()方法意味着线程将不再得到CPU时间。这两种暂停线程的方法是有区别的,Sleep()使得线程立即停止执行,但是在调用Suspend()方法之前,公共语言运行时必须到达一个安全点。一个线程不能对另外一个线程调用Sleep()方法,但是可以调用Suspend()方法使得另外一个线程暂停执行。对已经挂起的线程调用Thread.Resume()方法会使其继续执行。不管使用多少次Suspend()方法来阻塞一个线程,只需一次调用Resume()方法就可以使得线程继续执行。已经终止的和还没有开始执行的线程都不能使用挂起。Thread.Sleep(int x)使线程阻塞x毫秒。只有当该线程是被其他的线程通过调用Thread.Interrupt()或者Thread.Abort()方法,才能被唤醒。
    如果对处于阻塞状态的线程调用Thread.Interrupt()方法将使线程状态改变,但是会抛出ThreadInterupptedException异常,你可以捕获这个异常并且做出处理,也可以忽略这个异常而让运行时终止线程。在一定的等待时间之内,Thread.Interrupt()和Thread.Abort()都可以立即唤醒一个线程。

    下面我们将说明如何从一个线程中止另外一个线程。在这种情况下,我们可以通过使用Thread.Abort()方法来永久销毁一个线程,而且将抛出ThreadAbortException异常。使终结的线程可以捕获到异常但是很难控制恢复,仅有的办法是调用Thread.ResetAbort()来取消刚才的调用,而且只有当这个异常是由于被调用线程引起的异常。因此,A线程可以正确的使用Thread.Abort()方法作用于B线程,但是B线程却不能调用Thread.ResetAbort()来取消Thread.Abort()操作。
    Thread.Abort()方法使得系统悄悄的销毁了线程而且不通知用户。一旦实施Thread.Abort()操作,该线程不能被重新启动。调用了这个方法并不是意味着线程立即销毁,因此为了确定线程是否被销毁,我们可以调用Thread.Join()来确定其销毁,Thread.Join()是一个阻塞调用,直到线程的确是终止了才返回。但是有可能一个线程调用Thread.Interrupt()方法来中止另外一个线程,而这个线程正在等待Thread.Join()调用的返回。
    尽可能的不要用Suspend()方法来挂起阻塞线程,因为这样很容易造成死锁。假设你挂起了一个线程,而这个线程的资源是其他线程所需要的,会发生什么后果。因此,我们尽可能的给重要性不同的线程以不同的优先级,用Thread.Priority()方法来代替使用Thread.Suspend()方法。

    Thread类有很多的属性,这些重要的属性是我们多线程编程必须得掌握的。

    Thread.IsAlive属性:获取一个值,该值指示当前线程的执行状态。如果此线程已启动并且尚未正常终止或中止,则为 true;否则为 false。

    Thread.Name 属性:获取或设置线程的名称。

    Thread.Priority 属性:获取或设置一个值,该值指示线程的调度优先级。
    Thread.ThreadState 属性:获取一个值,该值包含当前线程的状态。
    在下面的例子中,我们将看看怎么设置这些属性,在随后的例子中我们将详细的讨论这些属性。
    创建一个线程,首先得实例化一个Thread类,在类得构造函数中调用ThreadStart委派。这个委派包含了线程从哪里开始执行。当线程启动后,Start()方法启动一个新的线程。下面是例子程序。
    using System;
    using System.Threading ;
    namespace LearnThreads
    {
    class Thread_App
    {
    public static void First_Thread()
    {
    Console.WriteLine("First thread created");
    Thread current_thread = Thread.CurrentThread;
    string thread_details = "Thread Name: " + current_thread.Name + "\r\nThread State: " + current_thread.ThreadState.ToString()+"\r\n Thread Priority level:"+current_thread.Priority.ToString();
    Console.WriteLine("The details of the thread are :"+ thread_details);
    Console.WriteLine ("first thread terminated");
    }

    public static void Main()
    {
    ThreadStart thr_start_func = new ThreadStart (First_Thread);
    Console.WriteLine ("Creating the first thread ");
    Thread fThread = new Thread (thr_start_func);
    fThread.Name = "first_thread";
    fThread.Start (); //starting the thread
    }
    }
    }

    在这个例子中,创建了一个fThread的线程对象,这个线程负责执行First_Thread()方法里面的任务。当Thread的Start() 方法被调用时包含First_Thread()的地址ThreadStart的代理将被执行。

    Thread状态
    System.Threading.Thread.ThreadState属性定义了执行时线程的状态。线程从创建到线程终止,它一定处于其中某一个状态。当线程被创建时,它处在Unstarted状态,Thread类的Start() 方法将使线程状态变为Running状态,线程将一直处于这样的状态,除非我们调用了相应的方法使其挂起、阻塞、销毁或者自然终止。如果线程被挂起,它将处于Suspended状态,除非我们调用resume()方法使其重新执行,这时候线程将重新变为Running状态。一旦线程被销毁或者终止,线程处于Stopped状态。处于这个状态的线程将不复存在,正如线程开始启动,线程将不可能回到Unstarted状态。线程还有一个Background状态,它表明线程运行在前台还是后台。在一个确定的时间,线程可能处于多个状态。据例子来说,一个线程被调用了Sleep而处于阻塞,而接着另外一个线程调用Abort方法于这个阻塞的线程,这时候线程将同时处于WaitSleepJoin和AbortRequested状态。一旦线程响应转为Sle阻塞或者中止,当销毁时会抛出ThreadAbortException异常。

    线程优先级
    System.Threading.Thread.Priority枚举了线程的优先级别,从而决定了线程能够得到多少CPU时间。高优先级的线程通常会比一般优先级的线程得到更多的CPU时间,如果不止一个高优先级的线程,操作系统将在这些线程之间循环分配CPU时间。低优先级的线程得到的CPU时间相对较少,当这里没有高优先级的线程,操作系统将挑选下一个低优先级 的线程执行。一旦低优先级的线程在执行时遇到了高优先级的线程,它将让出CPU给高优先级的线程。新创建的线程优先级为一般优先级,我们可以设置线程的优先级别的值,如下面所示:
    Highest
    AboveNormal
    Normal
    BelowNormal
    Lowest

    结论:在这一部分,我们讨论了线程的创建何线程的优先级。System.Threading命名空间还包含了线程锁定、线程同步何通讯、多线程管理类以及死锁解决等等高级特性,在后面的部分我们将继续讨论这些内容。

    源文档 <http://9host.cn/asp.net/2007411213758.html>


    上次做了个东西,用了多线程,现总结如下。
    使用多线程是为了提高CPU的利用率,即在在相同的时间里面做更多的事情(但前提是系统资源没有完全耗尽),ASP.NET中使用多线程可以加快页面在服务器端的生成速度。一般页面生成过程中花费时间最多的是数据库查询阶段,如果你的页面有10个查询,不使用多线程的话,这10个查询将是串行执行的——即依次执行每一个查询。如果使用多线程,将可以使这10个查询几乎同时执行。这显然会提高页面的生成速度。
    在网上搜索了些许帖子说在IIS进程中使用多线程是不稳定的,可我经过实践却发现ASP.NET使用多线程也没出啥问题。不过在ASP.NET中使用多线程得注意一些地方,不然确实是不稳定,甚至是行不通的。比如不能在多线程中使用HttpContext下的任何方法和属性,这就包括Cookie、Session、Response、Request、Application等等,当使用这些方法或者属性的时候,IIS进程将会直接崩溃。更要注意的是,由于多线程与页面的加载(Load)是异步执行的,必须让这些创建的线程在Load执行完之前同步,不然可能导致数据没有加载成功。 可能会有人问HttpContext等都被限制了,页面中还能做什么呢?我们完全可以把创建的线程与页面主体隔开,把需要的数据先在页面主体中获取,然后直接传入到创建的线程中就可解决。话不多说,具体如何,请看下文。
    假设某个页面中有2个SQL查询,一个是根据Url传递来的参数P确定当前页的内容,另一个查询是显示所有分类。第一个查询语句需要用到Request.QueryString获取P传递来的页码,第二个查询语句则可直接写SQL语句。
    假设第一个查询语句如:SELECT * FROM Archives WHERE Page=传递的页码
    假设第二个查询语句如:SELECT * FROM Category
    我们先创建一个类,用于接受参数P和数据绑定控件ID(此处使用Repeater控件绑定数据),此类能够把SQL语句查询的结果绑定到数据控件(Repeater)中。
    复制代码
    public classBindData
    {
        private int currentPage = 1;
        private Repeater rpID;

        public BindData(Repeater rpID)
        {
            this.rpID = rpID;
        }
        public BindData(Repeater rpID,int page)
        {
            this.rpID = rpID;
            this.currentPage = page;
        }  
       

        public void BindCategory()
        {
            string strSql="SELECT * FROM Category";
            this.BindDataToRepeater(strSql, this.rpID);

        }

        public void BindArchive()
        {
            string strSql = string.Format("SELECT * FROM Archives WHERE Page={0}",this.currentPage);
            this.BindDataToRepeater(strSql, this.rpID);
        }

        private void BindDataToRepeater(string strSql, Repeater rp)
        {
            if (rp == null) return;
            SqlConnection conn = new SqlConnection("data source=数据服务器地址;User ID=用户名;pwd=密码;Initial Catalog=数据库名");
            SqlCommand cmd = new SqlCommand(strSql, conn);
            SqlDataReader dtr;
            try

            {
                conn.Open();
                dtr = cmd.ExecuteReader();
                controlID.DataSource = rp;
                controlID.DataBind();
                if (!dtr.IsClosed)
                    dtr.Close();
            }
            catch { }
            finally
            {
                cmd.Dispose();
                if (conn.State = ConnectionState.Open)
                    conn.Close();
            }

        }
    }

    1. 上面创建的BindData类中有2个构造函数,分别用于绑定分类、绑定Arhive的不同形式。如果使用其他数据绑定控件则可进行相应修改;
    2. 创建了1个私有方法BindDataToRepeater用于把对应的SQL语句查询的结果绑定到对应的Repeater控件上。同时在此方法中使用了SqlDataReader,以提高绑定数据的速度。如果你使用了数据工厂可修改BindDataToRepeater中的具体实现过程;
    3. 2个共有方法BindCategory和BindArchive分别用于创建不同SQL语句、设置Repater的ID;
    4. 同时需要引入System.Web.UI、System.Web.UI.HtmlControls、System.Data.SqlClient3个必要的命名空间。
    值得注意的是在BindDataToRepeater方法中使用了try..catch语句,但并没有在catch块中做任何事情,为什么我们用try.catch却不在catch块中做点什么事情呢,不是多此一举吗?使用try..catch是为了防止在执行BindDataToRepeater时抛出异常,若此处出现异常且此方法是在多线程中执行的,将会导致IIS进程崩溃,进而影响其他页面的正常执行,故而用try...catch防止BindDataToRepeater抛出错误。
    我们之所以为数据绑定创建一个类,是为了提高内存利用率,当数据加载(Load)完毕的时候,为这个类创建的实例就会销毁。我们也可以通过在页面中创建几个全局变量来实现。但我还是建议以类的形式传递数据而不是使用全局变量。下面,我们开始在页面的Load中创建线程了。首先你需要在页面中引入System.Threading命名空间。
    复制代码
    protected void Page_Load(object sender, EventArgs e)
        {
            if (!IsPostBack)
            {
                int Page = 1;
                if(Request.QueryString["p"]!=null)
                    Page = Convert.ToInt32(Request.QueryString["p"]);

                BindData LoadArchives = new BindData(rpArhive,Page);
                Thread thArhives=new Thread(new ThreadStart(LoadArchives.BindArchive));
                thArhives.Start();
         
                BindData LoadCategory=new BindData(rpCategory);
                Thread thCategory = new Thread(new ThreadStart(LoadCategory.BindCategory));
                thCategory.Start();

                //进行页面的其他非数据绑定操作

                thArhives.Join();
                thCategory.Join();
              
               


            }
        }

    上面的代码显示在!IsPostBack状态下绑定数据。利用Request.QueryString获取了当前页码,并创建了BindData的2个实例LoadArchives、LoadCategory,通过 Thread thArhives=new Thread(new ThreadStart(LoadArchives.BindArchive))为绑定Arhice创建线程,通过Thread thCategory = new Thread(new ThreadStart(LoadCategory.BindCategory))为绑定分类创建线程,同时调用Thread的Start方法使2个线程进入执行状态。最后,在Load的最下面用Thread的Join方法使创建的2个线程与页面加载同步。
    值得注意的是,Join方法是必须的,如果不使用,可能导致创建的线程还未把数据完全绑定到Repeater上,Load就已经执行完毕,若如此页面上将没有任何数据。同时调用Start的代码行应尽量早,调用Join的代码行都应尽量迟——尽量放在Page_Load代码段的末尾,这样才能达到多线程的目的,若你每调用一个Start马上调用Join,其实质和没有使用多线程的效果是一样的。Join在MSND上的解释是:在继续执行标准的 COM 和 SendMessage 消息泵处理期间,阻塞调用线程,直到某个线程终止为止。
    只要设置好ASPX页面Repeater的绑定项,数据就可成功加载了。上面仅仅展示了2个SQL语句的查询,如果你有10个或者更多的SQL查询,在Page_Load中创建10个线程,让他们异步执行,最后用Join同步到Load,是一个提高性能的不错方法。

  • 相关阅读:
    Flink学习四:Flink运行架构
    Flink学习三:Flink安装
    Flink学习二:Flink基本架构
    进程的作业
    并发编程
    粘包解决模板
    网络下载作业
    网络通信远程操控
    网络编程套接字
    网络编程基础之网络协议篇
  • 原文地址:https://www.cnblogs.com/zhengguangITelite/p/2504195.html
Copyright © 2020-2023  润新知