C#中的多线程Thread

C#多线程Thread

Thread .net framework1.0提出的。Thread:是C#对计算机资源线程操作的一个封装类

 启动线程的几种方式

可以通过提供委托来启动线程,该委托表示线程在其类构造函数中执行的方法。 然后调用 Start方法开始执行。

线程执行的方法无参数

如果该方法没有参数,则将委托传递给ThreadStart 构造函数。

ThreadStart的签名:

public delegate void ThreadStart()  
private static void ThreadStart1()
        {
            ThreadStart threadStart = () =>
            {
                Console.WriteLine("ThreadStartMethod begin....");
            };
            Thread thread = new Thread(threadStart);
            thread.Start();//开启一个线程
            Console.WriteLine($"ThreadStart线程ID为:{ thread.ManagedThreadId}");
         }

线程执行的方法带参数

如果该方法具有参数,则将委托传递 ParameterizedThreadStart给构造函数。

 它具有签名:

public delegate void ParameterizedThreadStart(object obj)  
        /// <summary>
        /// 带一个参数【Lambda形式的委托】
        /// </summary>
        private static void ParameterizedThreadStart1()
        {
            //ParameterizedThreadStart是一个带一个形参的委托
            ParameterizedThreadStart method = s =>
            {
                Console.WriteLine($"ParameterizedThreadStart1传递的参数为{s.ToString()}");
                Console.WriteLine($"ParameterizedThreadStart1 thread id={Thread.CurrentThread.ManagedThreadId}");
            };
            Thread thread = new Thread(method);
            thread.Start("weiyin1");        
        }
        /// <summary>
        /// 带一个参数[实例化ParameterizedThreadStart委托,委托本质是一个类]
        /// </summary>
        private static void ParameterizedThreadStart2()
        {
            ParameterizedThreadStart method = new ParameterizedThreadStart(DoSomeThing);
            Thread thread = new Thread(method);
            thread.Start("weiyin2");
        }
    private static void DoSomeThing(object methodname)
        {
            Thread.Sleep(1000);
            Console.WriteLine($"{DateTime.Now.ToString()} method: {methodname},ThreadID=[{Thread.CurrentThread.ManagedThreadId}]");
        }
        static void Main(string[] args)
        {
            ThreadStart1();
            ParameterizedThreadStart1();
            ParameterizedThreadStart2();
            Console.WriteLine($"{DateTime.Now.ToString()} 当前主线程ID为:{Thread.CurrentThread.ManagedThreadId}");
        }

Thead线程等待、回调

 Thread提供了一系列的方法来操作执行的线程,如下所示:

//thread.Suspend();//暂停线程,微软已弃用
//thread.Resume();//恢复线程,微软已弃用,无法实时的暂停或者恢复线程
 //thread.Abort();//终结线程 抛出ThreadAbortException异常,以开始终止此线程的过程
 //Thread.ResetAbort();//都会有延时

同时也提供了线程等待的方法,如下:

           ////如果我们需要线程等待:
            //1.判断状态等待
            //while (thread.ThreadState != ThreadState.Stopped)
            //{
            //    Thread.Sleep(200);
            //}
            ////2.john等待
            //thread.Join();//主线程等待子线程计算完成,卡界面。
            ////可以限时等待
            //thread.Join(2000);//可以限时等待,等待2s,过时不候
            //thread.Priority = ThreadPriority.Highest;
            ////是不是就可以保证是优先执行呢?不能,其只是提供优先执行的概率,优先执行并不代表优先结束,千万不要用这个来控制线程执行的顺序;

如果我们想要控制线程执行顺序,上面给的thread.Priority = ThreadPriority.Highest;其实不靠谱,那如何实现了?

上篇博客我们提到了回调【一个动作执行完后,顺序执行另一个动作】,此处一样的道理。

1.通过while循环判断子线程的执行状态,当其状态为Stopped时(即子线程执行完毕了)后,执行另一个委托绑定的方法【此处为方便,都写的Lambda表达式】。

   此方法虽然可以确保两个委托绑定的方法顺序执行,但是第一个委托执行时,主线程一直在判断其状态,处于卡起的状态,且actionCallBack委托Invoke时线程为主线程。

        static void Main(string[] args)
        {
            Console.WriteLine($"{DateTime.Now.ToString()} 当前主线程ID为:{Thread.CurrentThread.ManagedThreadId}");
            ThreadStart threadStart = () => {
                Console.WriteLine("threadStart委托开始执行");
                Console.WriteLine($"{DateTime.Now.ToString()} 当前threadStart线程ID为:{Thread.CurrentThread.ManagedThreadId}");
                Console.WriteLine("threadStart委托执行完毕");
            };
            Action CallBackaction = () => {
                Console.WriteLine("CallBackaction委托开始执行");
                Console.WriteLine($"{DateTime.Now.ToString()} 当前CallBackaction线程ID为:{Thread.CurrentThread.ManagedThreadId}");
                Console.WriteLine("CallBackaction委托执行完毕");
            };
            ThreadWithCallBack(threadStart, CallBackaction);

 

private static void ThreadWithCallBack(ThreadStart threadStart, Action actionCallBack)
{
Thread thread = new Thread(threadStart); thread.Start(); { //主线程会一直while无限循环判断thread子线程的ThreadState状态(卡界面),等待thread子线程其执行完后,在执行后面的动作。 while (thread.ThreadState != ThreadState.Stopped) { Thread.Sleep(1000); } actionCallBack.Invoke(); }
}

2.采用join进行判断,与上面的类似,其也会卡顿主界面。

    Thread thread = new Thread(threadStart);
            thread.Start();
            thread.Join();//等待thread子线程执行完毕,其会卡界面
            actionCallBack.Invoke();

 3 用一个新的委托将这两个顺序执行的委托包起来,然后启动新的线程执行这个新的委托,实现其与主线程异步,但是内部两个委托执行按顺序同步。

            //把形参的threadStart和actionCallBack又包了一层。。ThreadStart是一个无参无返回值的委托
            //threadStart与actionCallBack的执行是按照先threadStart,后actionCallBack开始的,因为他们包在了
            //threadStart1里面是顺序同步的,同时threadStart1子线程与主线程是异步的。实现子线程内部同步按照指定顺序,主线程外面与子线程异步【即不卡界面】,。
            //多播委托也可
            ThreadStart threadStart1 = new ThreadStart(() =>
             {
                 threadStart.Invoke();
                 actionCallBack.Invoke();
             }
            );
            Thread thread = new Thread(threadStart1);
            thread.Start();

以上都是无返回值的委托,那如果是执行有返回值的委托呢?

private static void Test()
        {
            Func<int> func = () =>
            {
                return DateTime.Now.Year;
            };
            //int iresult = ThreadWithReturn(func);//能得到2020吗?
            Func<int> funcreuslt = ThreadWithReturn<int>(func);//不卡界面
            {
                Console.WriteLine("*******");
                Console.WriteLine("这里的执行也需要3秒钟");
            }

            int iresult = funcreuslt.Invoke();//这里会卡界面
        }
    private static Func<T> ThreadWithReturn<T>(Func<T> func)
        {
            T t = default(T);
            ThreadStart threadStart = new ThreadStart(() =>
            {
                t = func.Invoke();
            });
            Thread thread = new Thread(threadStart);
            thread.Start();//不卡界面
            Console.WriteLine("子线程ID为" + thread.ManagedThreadId);
            return new Func<T>(
                () =>
                {
                    thread.Join();//
                                  //此处这里写就是想必须等到子线程执行完毕后,才返回拿结果;有可能在这里都不用等待,上面的子线程start都执行完了
                    Console.WriteLine("Func线程ID为" + Thread.CurrentThread.ManagedThreadId);
                    return t;
                });

        }

注意此处ThreadWithReturn方法的返回值里面:thread.join,其是为了确保thread子线程执行完毕,而进行等待。

结果为:

 因为funcreuslt.Invoke()是在主线程的最后面执行的,所以里面委托绑定的方法也是在前面的打印信息之后执行。

Thead线程前台、后台线程

https://www.cnblogs.com/ryanzheng/p/10961777.html 

1、当在主线程中创建了一个线程,那么该线程的IsBackground默认是设置为FALSE的。

2、当主线程退出的时候,IsBackground=FALSE的线程还会继续执行下去,直到线程执行结束。

3、只有IsBackground=TRUE的线程才会随着主线程的退出而退出。

4、当初始化一个线程,把Thread.IsBackground=true的时候,指示该线程为后台线程。后台线程将会随着主线程的退出而退出。

5、原理:只要所有前台线程都终止后,CLR就会对每一个活在的后台线程调用Abort()来彻底终止应用程序。

下面是MSDN给的例子:

例子中创建了两个线程,foregroundThread【IsBackground=false】和backgroundThread [IsBackground=true],foregroundThread线程执行了RunLoop循环打印10次,Background打印50次,当foreground线程执行完后,主线程也执行完毕后,由于设置的backgroundThread

为后台线程,其会随着主线程的退出而退出,而不会继续去执行剩下的逻辑(打印其余次数操作)。

using System;
using System.Threading;

class Example
{
    static void Main()
    {
        BackgroundTest shortTest = new BackgroundTest(10);
        Thread foregroundThread = 
            new Thread(new ThreadStart(shortTest.RunLoop));

        BackgroundTest longTest = new BackgroundTest(50);
        Thread backgroundThread = 
            new Thread(new ThreadStart(longTest.RunLoop));
        backgroundThread.IsBackground = true;

        foregroundThread.Start();
        backgroundThread.Start();
    }
}

class BackgroundTest
{
    int maxIterations;

    public BackgroundTest(int maxIterations)
    {
        this.maxIterations = maxIterations;
    }

    public void RunLoop()
    {
        for (int i = 0; i < maxIterations; i++) {
            Console.WriteLine("{0} count: {1}", 
                Thread.CurrentThread.IsBackground ? 
                   "Background Thread" : "Foreground Thread", i);
            Thread.Sleep(250);
        }
        Console.WriteLine("{0} finished counting.", 
                          Thread.CurrentThread.IsBackground ? 
                          "Background Thread" : "Foreground Thread");
    }
}
// The example displays output like the following:
//    Foreground Thread count: 0
//    Background Thread count: 0
//    Background Thread count: 1
//    Foreground Thread count: 1
//    Foreground Thread count: 2
//    Background Thread count: 2
//    Foreground Thread count: 3
//    Background Thread count: 3
//    Background Thread count: 4
//    Foreground Thread count: 4
//    Foreground Thread count: 5
//    Background Thread count: 5
//    Foreground Thread count: 6
//    Background Thread count: 6
//    Background Thread count: 7
//    Foreground Thread count: 7
//    Background Thread count: 8
//    Foreground Thread count: 8
//    Foreground Thread count: 9
//    Background Thread count: 9
//    Background Thread count: 10
//    Foreground Thread count: 10
//    Background Thread count: 11
//    Foreground Thread finished counting.
View Code

TheadPool线程池

线程池,是在.net framework 2.0提出的,基于Thread做了个升级,Thread功能强大,但是让开发者用不好Thread:框架没有去控制线程数量:容易滥用,就疯狂开启线程数量,其实在开发中,业务处理不好,开线程让服务器扛不住;

池化思想:如果某个对象创建和销毁代价比较高,同时这个对象还可以反复使用的,就需要一个池子,保存多个这样的对象,需要用的时候从池子里面获取;用完之后不用销毁,放回池子。(享元模式)其节约资源提升性能;此外,还能管控总数量,防止滥用。

TheadPool线程池的使用、线程设置

 通过将waitCallback委托对象加入到ThreadPool.QueueUserWorkItem来启动一个线程(线程来自于线程池)

        /// <summary>
        /// 线程池
        /// 在.net framework 2.0基于Thread做了个升级
        /// Thread功能强大,但是让开发者用不好
        /// Thread:框架没有去控制线程数量:容易滥用,就疯狂开启线程数量,其实在开发中,业务处理不好,开线程让服务器扛不住;
        /// 池化思想:如果某个对象创建和销毁代价比较高,同时这个对象还可以反复使用的,就需要一个池子,保存多个这样的对象,需要用的时候从池子
        /// 里面获取;用完之后不用销毁,放回池子。(享元模式)
        /// 节约资源提升性能;此外,还能管控总数量,防止滥用。
        /// 
        /// </summary>
        private static void TreadPoolOperate()
        {
            Console.WriteLine("TreadPoolOperate主线程 start");
            Console.WriteLine($"主线程ID为:{Thread.CurrentThread.ManagedThreadId}");
            //1如何分配一个线程;
            WaitCallback waitCallback = a =>
            {
                Console.WriteLine($"waitCallback参数为{a}");
                Console.WriteLine("线程池分配的线程ID为:" + Thread.CurrentThread.ManagedThreadId);
                Console.WriteLine("TreadPoolOperating...");
            };
            //ThreadPool.QueueUserWorkItem(waitCallback);//无参数
            ThreadPool.QueueUserWorkItem(waitCallback, "waitCallback paramInfo");

可以通过ThreadPool提供的API对线程池中线程数目进行相关操作,如下所示:

{
                // 2设置线程池中线程数量
                //workerThreads:
                //     线程池中辅助线程的最大数目。
                //
                //   completionPortThreads:
                //     线程池中异步 I/O 线程的最大数目
                ThreadPool.SetMinThreads(8, 8);
                ThreadPool.SetMaxThreads(18, 18);//在之前最大线程不能低于计算机的线程数
                                                 //out int minworkTheead 新语法支持引用,相比之前简写了。
                ThreadPool.GetMinThreads(out int minworkTheead, out int mincompletionPortThreads);
                Console.WriteLine($"min this  workTheead= {minworkTheead},this completionPortThreads={mincompletionPortThreads}");
                ThreadPool.GetMaxThreads(out int maxworkTheead, out int maxcompletionPortThreads);
                Console.WriteLine($"max this  workTheead= {maxworkTheead},this completionPortThreads={maxcompletionPortThreads}");
                //线程池里的线程可以设置,但是不要随便折腾,因为设置以后,线程相对于当前线程而言,是全局的
                //Task parallel都是来自于线程池。但是new thread由可以新开一个线程,会占用一个线程池的位置
        }

ManualResetEvent线程等待

 ManualResetEvent被用于在两个或多个线程间进行线程信号发送。  多个线程可以通过调用ManualResetEvent对象的WaitOne方法进入等待或阻塞状态。当控制线程调用Set()方法,所有等待线程将恢复并继续执行。

ManualResetEvent对象的初始化

  

    //
    // 摘要:
    //     通知一个或多个正在等待的线程已发生事件。 此类不能被继承。
    [ComVisible(true)]
    public sealed class ManualResetEvent : EventWaitHandle
    {
        //
        // 摘要:
        //     用一个指示是否将初始状态设置为终止的布尔值初始化 System.Threading.ManualResetEvent 类的新实例。
        //
        // 参数:
        //   initialState:
        //     如果为 true,则将初始状态设置为终止;如果为 false,则将初始状态设置为非终止。
        public ManualResetEvent(bool initialState);
    }

其构造函数传入一个bool值,如果bool值为False,则使所有线程阻塞,反之,如果bool值为True,则使所有线程退出阻塞。其默认初始值为False

ManualResetEvent mre = new ManualResetEvent(false);

WaitOne方法的使用

  该方法阻塞当前线程并等待其他线程发送信号。如果收到信号,它将返回True,反之返回False。以下演示了如何调用该方法。

该方法存在于abstract class WaitHandle类中,是一个虚方法。

API信息如下【其还有很多重载方法,此处不赘述】:

    //
        // 摘要:
        //     阻止当前线程,直到当前 System.Threading.WaitHandle 收到信号。
        //
        // 返回结果:
        //     如果当前实例收到信号,则为 true。 如果当前实例永不发出信号,则 System.Threading.WaitHandle.WaitOne(System.Int32,System.Boolean)
        //     永不返回。
        //
        // 异常:
        //   T:System.ObjectDisposedException:
        //     已释放当前实例。
        //
        //   T:System.Threading.AbandonedMutexException:
        //     等待结束,因为线程在未释放互斥的情况下退出。 在 Windows 98 或 Windows Millennium Edition 上不会引发此异常。
        //
        //   T:System.InvalidOperationException:
        //     当前实例是另一个应用程序域中的 System.Threading.WaitHandle 的透明代理。
        public virtual bool WaitOne();
View Code

Set方法

 该方法用于给所有等待线程发送信号。 Set() 方法的调用使得ManualResetEvent对象的bool变量值为True,所有线程被释放并继续执行。

Reset方法
   一旦我们调用了ManualResetEvent对象的Set()方法,它的bool值就变为true,我们可以调用Reset()方法来重置该值,Reset()方法重置该值为False。

 

此处初始化了一个值为False的ManualResetEvent对象,这意味着所有调用WaitOne的线程将被阻塞【此例主线程调用了WaitOne】,直到有线程调用了 Set() 方法【此处子线程语句最后将事件信号状态变为了True,主线程WaitOne就能继续执行,而不用等待了】。

    private static void TreadPoolOperate()
        {
            Console.WriteLine("TreadPoolOperate主线程 start");
            Console.WriteLine($"主线程ID为:{Thread.CurrentThread.ManagedThreadId}");
                //3线程等待:观望式  
                //ManualResetEvent默认要求参数状态为false--mre.set();打开
                //ManualResetEvent 参数状态为true->open-->mre.reset();关闭
                ManualResetEvent mre = new ManualResetEvent(false);//如果为 true,则将初始状态设置为终止;如果为 false,则将初始状态设置为非终止
                ThreadPool.QueueUserWorkItem(a =>
                {
                    Console.WriteLine(a);
                    Console.WriteLine("当前子线程ID为" + Thread.CurrentThread.ManagedThreadId);
                    Console.WriteLine("线程池里面的线程执行完毕");
                    //将事件状态设置为有信号,从而允许一个或多个等待线程继续执行。
                    mre.Set();//信号由false变为true 
                });
                Console.WriteLine("do something else");
                Console.WriteLine("do something else");
                Console.WriteLine("do something else");
                //// 阻止当前线程,直到当前 System.Threading.WaitHandle 收到信号。
                mre.WaitOne();//判断信号,没有信号(为false),继续等起,有信号了(mre.Set()后,信号为true),不用等待了,可以往后执行了
//只要mre的状态变为true,则继续往后执行 Console.WriteLine("全部任务执行完成");

 

而如果我们用值True来对ManualResetEvent对象进行初始化,所有调用WaitOne方法的线程并不会被阻塞,可以进行后续的执行。

Thead、TheadPool扩展封装

 那如果初始状态设置为无信号,后面又执行WaitOne无限等待,那就会出现死锁的状态。如下例所示:

#region myregion
                {
                    ThreadPool.SetMaxThreads(32, 32);//
                    ManualResetEvent mre1 = new ManualResetEvent(false);
                    for (int i = 0; i < 32; i++)
                    {
                        int k = i;
                        ThreadPool.QueueUserWorkItem(t =>
                        {
                            Console.WriteLine($"thread ID={Thread.CurrentThread.ManagedThreadId}");
                        });
                        if (k == 31)
                        {
                            mre1.Set();
                        }
                        else
                        {
                            mre1.WaitOne();//死锁了
                            Console.WriteLine("mre1.WaitOne()");
                        }
                    }
                    mre1.WaitOne();
                    Console.WriteLine("所有任务执行完成");

                }
                #endregion

 

 
posted @ 2020-12-27 15:46  liweiyin  阅读(509)  评论(0编辑  收藏  举报