c#学习笔记之多线程(未完)

Thread

进程是执行的程序,是系统分配资源的基本单元,一个进程中有多个线程。

线程是cpu分派和调度的基本单元,共享进程资源。

多线程就好比在qq文字聊天的同时外面能发送文件,这两个可以同时进行。

创建线程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sTdKLGrV-1626439614472)(异步编程.assets/image-20210715195908308.png)]

官方文档可以看出线程的构造函数接受两种类型的委托

public Thread (System.Threading.ParameterizedThreadStart start);
public Thread (System.Threading.ThreadStart start);

ParameterizedThreadStart 委托

public delegate void ParameterizedThreadStart(object? obj);
  • ParameterizedThreadStart委托在创建线程时可以接受 带有一个参数Object 不带返回值 的方法。
  • 传入委托的方法参数一定是 Object 哦。
  • 话说是只有一个参数,但是这可是Object类型啊,你想要传多个数据可以把数据封装成class,传个对象过去,
  • 虽然不能ref 和 out ,但是你传class对象的话,改不了对象的引用但是可以改引用堆地址处的数据啊,所以可以通过另一种途径获取返回值。
  • 另外有传递数据更简便的方法是传lambda表达式,不过其实lambda表达式
Example:

主线程和创建的线程异步遍历输出10次,myNum的数据在创建的线程中改变。

namespace Thread_CreateTest
{
    class MyNum
    {
        public double length { set; get; }
        public double width { set; get; }
        public double area { get { return width * length; } }
    }
    class Program
    {
        public static void ThreadMethod1(Object obj)
        {
            if (obj is MyNum)
            {
                MyNum myNum = (MyNum)obj;
                myNum.width = 10;
                myNum.length = 4.5;
            }
            else
            {
                Console.WriteLine(obj);
            }
            for (int i = 1; i < 10; i++)
            {
                Console.WriteLine($"-- ThreadMethod1 -- {Thread.CurrentThread.Name}\t {Thread.CurrentThread.IsThreadPoolThread}\t ");
            }
        }
        static void Main(string[] args)
        {
            Thread.CurrentThread.Name = "Main_name";
            Thread myThread1 = new Thread(ThreadMethod1);
            myThread1.Name = "myThread1_name";
            MyNum myNum = new MyNum();
            myThread1.Start(myNum);
            
            for (int i = 1; i < 10; i++)
            {
                Console.WriteLine($"-- Main -- {Thread.CurrentThread.Name}\t {Thread.CurrentThread.IsThreadPoolThread}\t ");
            }
            myThread1.Join();
            Console.WriteLine($"myNum length:{myNum.length}\t width:{myNum.width}\t area:{myNum.area}");
            Console.ReadKey();
            //--Main-- Main_name False
            //--Main-- Main_name False
            //--ThreadMethod1-- myThread1_name False
            //--ThreadMethod1-- myThread1_name False
            //--ThreadMethod1-- myThread1_name False
            //--ThreadMethod1-- myThread1_name False
            //--ThreadMethod1-- myThread1_name False
            //--ThreadMethod1-- myThread1_name False
            //--ThreadMethod1-- myThread1_name False
            //--ThreadMethod1-- myThread1_name False
            //--ThreadMethod1-- myThread1_name False
            //--Main-- Main_name False
            //--Main-- Main_name False
            //--Main-- Main_name False
            //--Main-- Main_name False
            //--Main-- Main_name False
            //--Main-- Main_name False
            //--Main-- Main_name False
            //myNum length:4.5         width: 10        area: 45
        }
       
    }
}

ThreadStart 委托

public delegate void ThreadStart();

ThreadStart委托在创建线程时 不接受有参数有返回值的方法

使用和ParameterizedThreadStart差不多,只是没有参数而已,不赘述。

传递参数

在Thread构造函数中稍稍介绍了一下,这里简单举例:

    class Program
    {
        static void Main(string[] args)
        {
            var sample = new ThreadArgs(10);

            // plan1 将要传递的参数设为实例对象的字段或属性,为实例的无参方法开线程
            var threadOne = new Thread(sample.CountNumbers);
            threadOne.Name = "ThreadOne";
            threadOne.Start();
            threadOne.Join();

            Console.WriteLine("--------------------------");

            // plan2 带一个参数的ParameterizedThreadStart委托,线程Start时传参
            var threadTwo = new Thread(Count);
            threadTwo.Name = "ThreadTwo";
            threadTwo.Start(8);
            threadTwo.Join();

            Console.WriteLine("--------------------------");

            // plan3 lambda表达式
            var threadThree = new Thread(() => CountNumbers(12));
            threadThree.Name = "ThreadThree";
            threadThree.Start();
            threadThree.Join();
            Console.WriteLine("--------------------------");

            // lambda变量捕获有一个弊端就是 外部变量会被捕获它的所有方法共享,一处变处处变
            int i = 10;
            var threadFour = new Thread(() => PrintNumber(i));
            i = 20;
            var threadFive = new Thread(() => PrintNumber(i));
            threadFour.Start();
            // 20
            threadFive.Start();
            // 20
            Console.ReadKey();
        }

        static void Count(object iterations)
        {
            CountNumbers((int)iterations);
        }

        static void CountNumbers(int iterations)
        {
            for (int i = 1; i <= iterations; i++)
            {
                Thread.Sleep(TimeSpan.FromSeconds(0.5));
                Console.WriteLine("{0} prints {1}", Thread.CurrentThread.Name, i);
            }
        }

        static void PrintNumber(int number)
        {
            Console.WriteLine(number);
        }

        class ThreadArgs
        {
            private readonly int _iterations;

            public ThreadArgs(int iterations)
            {
                _iterations = iterations;
            }
            public void CountNumbers()
            {
                for (int i = 1; i <= _iterations; i++)
                {
                    Thread.Sleep(TimeSpan.FromSeconds(0.5));
                    Console.WriteLine("{0} prints {1}", Thread.CurrentThread.Name, i);
                }
            }
        }
    }

相关方法

join()

在调用join()的实例线程终止前阻止调用当前线程,实现两个线程之间同步执行。

虽然加上了超时时间,但是该时间内没有终止也只是会返回false并不会去终止它。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lxxEdp5N-1626439614475)(多线程.assets/image-20210716094418352.png)]

public void Join ();
public bool Join (int millisecondsTimeout);// 毫秒数
public bool Join (TimeSpan timeout);

TimeSpan

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hapMIwNQ-1626439614477)(多线程.assets/image-20210716094704343.png)]

public class Example
{
    static Thread thread1, thread2;

    public static void Main()
    {
        thread1 = new Thread(ThreadProc);
        thread1.Name = "Thread1";
        thread1.Start();

        thread2 = new Thread(ThreadProc);
        thread2.Name = "Thread2";
        thread2.Start();

        Console.ReadKey();
    }

    private static void ThreadProc()
    {
        Console.WriteLine("\nCurrent thread: {0}", Thread.CurrentThread.Name);
        if (Thread.CurrentThread.Name == "Thread1" &&
            thread2.ThreadState != ThreadState.Unstarted)
            Console.WriteLine($"Is Thread2 finsh:{thread2.Join(new TimeSpan(0,0,1))}");

        Thread.Sleep(4000);
        Console.WriteLine("\nCurrent thread: {0}", Thread.CurrentThread.Name);
        Console.WriteLine("Thread1: {0}", thread1.ThreadState);
        Console.WriteLine("Thread2: {0}\n", thread2.ThreadState);
    }
}
// The example displays output like the following:
        //Current thread: Thread1

        //Current thread: Thread2
        //Is Thread2 finsh:False

        //Current thread: Thread2
        //Thread1: WaitSleepJoin
        //Thread2: Running


        //Current thread: Thread1
        //Thread1: Running
        //Thread2: Stopped

sleep()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3h56gFcz-1626439614479)(多线程.assets/image-20210716101012539.png)]

参数的值为零,则该线程会将其时间片的剩余部分让给任何已经准备好运行的、具有同等优先级的线程。 如果没有其他已经准备好运行的、具有同等优先级的线程,则不会挂起当前线程的执行。

前后台线程

  • 平时我们显式创建的都是前台线程,ThreadPool和Task的都是后台线程,可以通过设置IsBackground = true来手动设置后台线程。
  • 进程会等待所有的前台线程完成后再结束工作,但是如果只剩下后台线程,则会直接结束工作。
  • 如果程序定义了一个不会完成的前台线程,主程序并不会正常结束。

线程安全

本地资源

共享资源

  • 多个线程引用到同一个对象的实例,那么就会共享该实例的数据
  • 静态字段也会在线程间共享
  • 被lambda表达式或者匿名委托捕获的本地变量,也会被编译器转为字段,也会被共享。

有共享变量在多线程调度时就容易产生线程安全。

Example:

两个线程模拟两个窗口卖票,票是共享数据,这里用实例对象实现共享

namespace Thread_SafeTest
{
    class Ticket
    {
        public int ticketNum{set;get;}
        public Ticket(int maxnum)
        {
            ticketNum = maxnum;
        }
    }
    class Program
    {

        static void Main(string[] args)
        {
            Ticket ticket = new Ticket(100);
            Thread thread1 = new Thread(SaleTicket);
            thread1.Name = "Thread1";
            Thread thread2 = new Thread(SaleTicket);
            thread2.Name = "Thread2";
            thread1.Start(ticket);
            thread2.Start(ticket);

            Console.WriteLine("Main--");
            Console.ReadKey();
        }
        public static void SaleTicket(Object ticketObj)
        {
            Ticket tickets = ticketObj as Ticket;
            if (tickets != null)
            {
                while(tickets.ticketNum!=0)
                    Console.WriteLine($"{Thread.CurrentThread.Name} --- saled --- {tickets.ticketNum--}");
            }
        }
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oVu69PDK-1626439614481)(多线程.assets/image-20210716104413014.png)]

结果可以看出,多线程对共享资源的访问引发了安全问题,thread1 运行到 票数自减时cpu资源被抢占,Thread2在Thread1票减一前拿到了资源就造成这种卖同一张票。

如何解决

加锁,lock可以基于任何引用类型对象。

如果锁定了一个对象,需要访问该对象的所有其他线程则会处于阻塞状态,直到该对象解除锁定。不过这可能会导致严重的性能问题。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JKwEN366-1626439614482)(多线程.assets/image-20210716160457427.png)]

信号

ThreadPool

ThreadPool线程都是后台线程,前台线程结束不论后台线程执行完成与否都会结束,finally也不会执行。

ThreadPool中有若干数量的线程,如果有任务需要处理时,会从线程池中获取一个空闲的线程来执行任务,任务执行完毕后线程不会销毁,而是被线程池回收以供后续任务使用。当线程池中所有的线程都在忙碌时,又有新任务要处理时,线程池才会新建一个线程来处理该任务,如果线程数量达到设置的最大值,任务会排队,等待其他任务释放线程后再执行。线程池能减少线程的创建,节省开销。

Thread是基于操作系统级别的线程,而ThreadPool和Task不会创建自己的操作系统线程,二者是由任务调度器(TaskScheduler)执行,默认的调度程序仅仅在ThreadPool上运行,

ThreadPool也可以创建线程,把方法加入到执行队列中等待

ThreadPool.QueueUserWorkItem 方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B7tJH8zO-1626439614483)(多线程.assets/image-20210716160656800.png)]

	class Program
    {
        static void Main(string[] args)
        {
            ThreadPool.QueueUserWorkItem((Object obj)=>Console.WriteLine($"Is ThreadPool :{Thread.CurrentThread.IsThreadPoolThread}"));
            Console.WriteLine("Main");
            Console.ReadKey();
        }
    }

需要传方法参数时可以用第二个方法第二个参数传。

Task

Task的线程也是ThreadPool里的线程,也是后台线程

与ThreadPool不同,Task可以在指定时间返回完成结果,并且还可以通过ContinueWith延续任务,以使得任务执行完毕后运行更多操作,如果已完成立即进行回调,也可以调用Wait来同步等待任务完成,如同Thread.Join一样阻塞线程执行,直到任务完成

Task创建线程三种方式

    class Program
    {
        static void Main(string[] args)
        {
            //1.new方式实例化一个Task,需要通过Start方法启动
            Task task = new Task(() =>
            {
                Thread.Sleep(100);
                Console.WriteLine($"hello, task1的线程ID为{Thread.CurrentThread.ManagedThreadId} 是线程池线程{Thread.CurrentThread.IsThreadPoolThread}");
            });
            task.Start();

            //2.Task.Factory.StartNew(Action action)创建和启动一个Task
            Task task2 = Task.Factory.StartNew(() =>
            {
                Thread.Sleep(100);
                Console.WriteLine($"hello, task2的线程ID为{ Thread.CurrentThread.ManagedThreadId} 是线程池线程{Thread.CurrentThread.IsThreadPoolThread}");
            });

            //3.Task.Run(Action action)将任务放在线程池队列,返回并启动一个Task
            Task task3 = Task.Run(() =>
            {
                Thread.Sleep(100);
                Console.WriteLine($"hello, task3的线程ID为{ Thread.CurrentThread.ManagedThreadId} 是线程池线程{Thread.CurrentThread.IsThreadPoolThread}");
            });
            Console.WriteLine("执行主线程!");
            Console.ReadKey();

            //执行主线程!
            //hello, task1的线程ID为3 是线程池线程True
            //hello, task2的线程ID为4 是线程池线程True
            //hello, task3的线程ID为5 是线程池线程True

        }
    }

Task阻塞

Task延续操作

async/await

C# 5.0之后引入了asyncawait关键字,在语言层面给予了并发更好的支持。

async用于标记异步方法:

  • async关键字是上下文关键字,只有在修饰方法Lambda时才会被当作关键字处理,在其它区域将被作为标识符处理。
  • async关键字可以标记静态方法,但不能标记入口点(Main()方法)。
  • async标记的方法返回值必须为TaskTask<TResult>void其中之一。

await用于等待异步方法的结果:

  • await关键字同样是上下文关键字,只有在async标记的方法中才被视为关键字。
  • await关键字可以用在async方法和TaskTask<TResult>之前,用于等待异步任务执行结束

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9lxpev6n-1626439614484)(多线程.assets/2a0638cddd6442f3a93d387706fa87d.png)]

并不是方法使用async关键字标记了就是异步方法,直接出现在async方法内部的语句也是同步执行的,异步执行的内容需要使用Task类执行。
事实上,一个不包含任何await语句的async方法将是同步执行的,此时编译器会给出警告。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-znelSwEP-1626439614485)(多线程.assets/image-20210715103210425.png)]

在上图中main输出结束了还等待了await完成,是不是说明主线程会等待异步完成再结束呢??

这就要分情况讨论啦,上例中异步调用返回值是void,主线程是并不会等待它异步完成再结束的

异步方法返回类型

  • void

  • Task

  • Task< T>

  • await关键字同样是上下文关键字,只有在async标记的方法中才被视为关键字。

  • await关键字可以用在async方法和TaskTask<TResult>之前,用于等待异步任务执行结束

[外链图片转存中…(img-9lxpev6n-1626439614484)]

并不是方法使用async关键字标记了就是异步方法,直接出现在async方法内部的语句也是同步执行的,异步执行的内容需要使用Task类执行。
事实上,一个不包含任何await语句的async方法将是同步执行的,此时编译器会给出警告。

[外链图片转存中…(img-znelSwEP-1626439614485)]

在上图中main输出结束了还等待了await完成,是不是说明主线程会等待异步完成再结束呢??

这就要分情况讨论啦,上例中异步调用返回值是void,主线程是并不会等待它异步完成再结束的

异步方法返回类型

  • void
  • Task
  • Task< T>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值