【.Net】结合项目谈谈多线程

提到多线程, 大家都知道, 在进程中启用多个线程进行工作, 会提升程序的效率等等。

本篇文章旨在解释多线程的基础概念之外, 还要结合实际的项目来谈多线程的具体使用。

 Thread

我们知道启动一个线程最直观的方法就是使用Thread类,f12看一下这个类的构造函数, 不多如下图:

使用thread要用两个参数, 一个是ThreadStart, 一个是ParameterizedThreadStart。这俩的区别是一个可以带参数一个可以不带参数, 如下面代码:

 

class demo1
    {
        public void test()
        {
            ThreadStart threadStart = new ThreadStart(Calculate);
            Thread thread = new Thread(threadStart);
            thread.Start();
        }

        public void Calculate()
        {
            double Diameter = 0.5;
            Console.Write("The perimeter Of Circle with a Diameter of {0} is {1}", Diameter, Diameter * Math.PI);
        }
    }

public void test
    {
        ParameterizedThreadStart threadStart=new ParameterizedThreadStart(Calculate);
        Thread thread=new Thread();
        thread.Start(0.9); 
    }

    public void Calculate(object arg)
    {
        double Diameter=double(arg);
        Console.Write("The perimeter Of Circle with a Diameter of {0} is {1}",
        Diameter,Diameter*Math.PI);
    }

 

ps: 这两个例子来源于网上。

实际项目中很多都是需要传递参数的情况。可以将需要的参数和线程执行的方法放到一个类中去, 调用的时候声明一个类的实例, 然后初始化属性, 方法执行时直接使用类中初始化好的属性来执行, 比如下面这个例子:

public class MyThread
{
    public double Diameter = 10;
    public double Result = 0;

    public MyThread(int Diameter)
    {
        this.Diameter = Diameter;
    }

    public void Calculate()
    {
        Console.WriteLine("Calculate Start");
        Thread.Sleep(2000);
        Result = Diameter * Math.PI; ;
        Console.WriteLine("Calculate End, Diameter is {0},Result is {1}", this.Diameter, Result);
    }
}

MyThread t=new MyThread(5.0);
ThreadStart threadStart=new ThreadStart(t.Calculate)
Thread thread=new Thread(threadStart);
thread.Start(); 
}

 Ps:在不想写类的时候可以写个匿名函数代替, 这里对匿名函数不做赘述了。可以参考匿名函数

要注意的是匿名方法过长会造成程序可读性降低。

但是项目需求更多的是需要传递参数而且需要从线程调用完方法完毕后返回执行结果等。如果是多个子线程, 那么这里面涉及到的技术问题是如何保证线程同步

线程池ThreadPool

线程池的使用是非常简单的,如下面的代码,把需要执行的代码提交到线程池,线程池内部会安排一个空闲的线程来执行你的代码,完全不用管理内部是如何进行线程调度的。

ThreadPool.QueqeUserWorkItem(t=>Console.WriteLine("Hello thread pool"));

每个CLR都有一个线程池,线程池在CLR内可以多个AppDomain共享,线程池是CLR内部管理的一个线程集合,初始是没有线程的,在需要的时候才会创建。线程池的主要结构图如下,基本流程如下:

 1.线程池内部维护一个请求列队,用于缓存用户请求需要执行的代码任务,就是Thread.QueqeUserWorkItem提交的请求;

 2.有新任务后,线程池使用空闲线程或者新线程来执行队列请求;

 3.任务执行完后线程不会销毁,留着重复使用;

 4.线程池自己负责维护线程的创建和销毁,但线程池有大量闲置的线程是,线程池会自动结束一部分多余的线程来释放资源;

线程池是有一个容量的,因为他是一个池子吗,可以设置线程池的最大活跃线程数,调用方法Threadpool.SetMaxThreads可是设置相关参数,但很多线程实践里都不建议程序猿们自己去设置这些参数,其实微软为了提高线程池性能,做了大量的优化,线程池可以很智能的确定是否要创建或者消费线程,大多数情况都可以满足需求了。线程池使得线程可以充分有效的被利用,减少了任务启动的延迟,也不用大量的去创建线程,避免了大量线程的创建和销毁对性能造成极大影响。

上面了解了线程的基本原理和诸多优点后,如果你是一个爱思考的猿类,应该会很容易的发现很多疑问,比如把任务添加到线程池队列后,怎么取消和挂起呢?如何知道他执行完了呢,下面来总结一下线程池的不足:

 1.线程池内的线程不支持线程的挂起、取消等操作,如想要取消线程池的任务,.net支持一种协作方式取消,使用起来也不少很方便,而且有些场景并不是很满足需求;

 2.线程内的任务没有返回值,也不知道何时执行完成;

 3.不支持设置线程的优先级,还包括其他类似需要对线程有更多的控制的需求也不支持;

因此微软为我们提供了另外一个东西叫做Task来补充线程池的某些不足。

任务Task与并行Parallel

 任务Task与并行Parallel本质上内部都是使用的线程池,提供了更丰富的并行编程的方式。任务Task基于线程池,可支持返回值,支持比较强大的任务执行计划定制等功能,下面实例。Task提供了很多方法和属性,通过这些方法和属性能够对Task的执行进行控制,并且能够获得其状态信息。Task的创建和执行都是独立的,因此可以对关联操作的执行拥有完全的控制权。

 待续....

//创建一个任务
Task<int> t1 = new Task<int>(n =>
{
    System.Threading.Thread.Sleep(1000);
    return (int)n;
}, 1000);
//定制一个延续任务计划
t1.ContinueWith(task =>
{
    Console.WriteLine("end" + t1.Result);
}, TaskContinuationOptions.AttachedToParent);
t1.Start();
//使用Task.Factory创建并启动一个任务
var t2 = System.Threading.Tasks.Task.Factory.StartNew(() =>
{
    Console.WriteLine("t1:" + t1.Status);
});
Task.WaitAll();
Console.WriteLine(t1.Result);

 

 并行Parallel内部其实使用的是Task对象(TPL会在内部创建Task的实例),所以的并行任务完成后才会返回。少量短时间任务建议就不要使用并行Parallel了,并行Parallel本身也是有性能开销的,而且还要进行并行任务调度、创建调用方法的委托等。

 

posted @ 2018-09-05 22:59  YanyuWu  阅读(186)  评论(0)    收藏  举报