C#多线程操作的学习

开篇:所有的线程虽然在微观上是串行执行的(例如你在主函数也就是主线程中肯定是先后调用两个线程执行的),但是在宏

观上你完全可以认为它们在并行执行。如果没有jion()或者WaiteOne(),等,阻塞主线程,那么他两个线程其实是同时执行的,只

是起点时间不同。

当按照先后顺序开启两个线程的时候,第一先开启的线程,线程内部任务还未执行完的时候,其实第二个语句已经开始,第二
个语句就是开启第二,那么第二个线程内部任务会和线程一的任务并发执行。优先级会改变他们的执行频率。
一、关于开启线程方法两种:
第一种:
Thread t = new Thread(Worker);
             t.start();
void Worker()
{
  console.WriteLine("这里是子线程要执行的任务");
}
第二种:
实例化一个ThreadStart ,无参代理
 ThreadStart t_start= new ThreadStart(YouWork)
   Thread t=new Thread (t_start);
有参数传递,委托,创建线程
 ParameterizedThreadStart Para_startThread = new ParameterizedThreadStart(Para_add);//代理委托要执行的函数,参数
是Object类型的函数
                 ThirdThread = new Thread(Para_startThread);//生成一个线程
                ThirdThread.Start(Ap);//具体参数在这里传递,这里传递的是一个类对象,这个类对象包含,函数体计算时需要的
参数,同样也可以传递结构体
函数体:
 void Para_add(object Data)//必须具有object类型的形参的函数,可以被ParameterizedThreadStart代理
        {
            if (Data is threadlern)
            {
                threadlern MyData = (threadlern)Data;
                Console.WriteLine("当前线程名称:"+ ThirdThread.Name+""+"当前线程
ID:"+Thread.CurrentThread.ManagedThreadId);
                Console.WriteLine("MyData.Num:" + MyData.Num+ "MyData.Num02:"+ MyData.Num02);
            }
        }
第三种 线程池
后面文章补充
二、关于前台和后台线程
线程池开启的线程默认是后台线程,上面两种开启方法,默认开启的是前台线程。
前台线程意思就是不和主线程或进程同时结束了,当主线程或进程结束的时候,前台子线程,会让主线程先“回家”,也就是让主
线程先结束,子线程这边执行完了自己结束。
后台线程,就是当主线程结束的时候,不管这个后台子线程有没有“把活儿干完”,你都得结束。设置后台线程的方法:
t.IsBackground=true;(这是一个可以get,也可以set的属性)
这样有个好处,避免你要结束程序的时候,某个子程序任务还没有完毕,主程序虽然结束,但子程序还在执行,消耗内存。
注:一旦这个线程使用了t.join();那么不管前台线程也好后台也好,主线程和子线程的关系变为:主线程必须等子线程把“活儿‘
干完,主线程和子线程才能结束,而且是一起结束。关系很亲密。
官方例子:
using System;
using System.Threading;
public static class Program {
    public static void Main() {
    // Create a new thread (defaults to foreground)
    Thread t = new Thread(Worker);
    // Make the thread a background thread
    t.IsBackground = true;
    t.Start(); // Start the thread
    // If t is a foreground thread, the application won't die for about 10 seconds
    // If t is a background thread, the application dies immediately
    Console.WriteLine("Returning from Main");
    }
    private static void Worker() {
    Thread.Sleep(10000); // Simulate doing 10 seconds of work
    // The line below only gets displayed if this code is executed by a foreground thread
    Console.WriteLine("Returning from Worker");
    }
}
三、关于多线程中使用的一些属性方法( t.Join()和Thread.sleep(1000)和优先级Priority;
1. t.Join():等待某子线程这个命令的作用,不管你是前台线程也好,后台线程也好,主线程必须等待子线程结束后再结束,同
样可以在这里指定int型参数确定最大等待时间。阻塞调用其它线程,直到调用join();的这个线程结束为止。同样还有另外一个办
法。从测试中我们可以很清楚的看到MainThread在NewThread.Join被调用后被阻塞,直到NewThread 执行完毕才继续执行。 
注意:当某个子线程调用Jion(),意思是告诉【主线程】等待他执行完,在开始下一句,不管下一句代码是什么。主要说的是【主
线程】和【子线程】的相互关系。
2.Thread.sleep(1000);这个是Thread中的静态方法,与线程实例对象无关,所以sleep方法写在那个线程执行的过程中,就让那
个线停留时间。
3.t.Abort();以上两种主程序等待子程序,或者让某个程序休眠,这些操作执行完毕后都可以把原来的程序恢复,进而继续执行
。但是由某个子程序对象调用的Abort()执行完毕后,意思是不可恢复性的终止该程序,通知 CLR立即终止。只有重新创建新线
程。
4.线程优先级Priority,所有的线程虽然在微观上是串行执行的,因为出发时间点不同,但是在宏观上你完全可以认为它们在并
行执行。线程的优先级只是把线程活动的重要程度给CLR,因此优先级高的并不能保证一定先执行。如果中间线程调度器被某
个任务抢占了优先级会被修改,就像概率事件一样,如果优先级高,执行的频率就高,因为调度器给予高优先级的时间片不一
样。多数情况下不需要修改,使用时小心。
5.t.Name,用线程实例对象名调用,这是一个可以get和set的属性。
6.t.IsBackground,读取或设置BOOL型的变量,确定本线程是不是后台
7.t.start() //告诉CLR,请尽快开启当前线程
8.t.ThreadState,如:Thread Program_T = Thread.CurrentThread;
                        Console.WriteLine(Program_T.ThreadState);
获取当前线程运行状态,如果不是running,可能是结束或者休眠。
四,关于线程开启的时候,参数传递的另一种方法
最好使用无参开启, ThreadStart t_start= new ThreadStart(YouWork),需要参数传递,使用中转代理函数来传递参数,或者使用
ThreadStart t_start= new ThreadStart(YouWork)
   Thread t=new Thread(t_start);
      t.start();
  private void YouWork()//一级代理无参数函数
   {    
   youwork_ori(5,100)
    }
  youwork_ori(int a,int b)//真正要执行的任务
{
   console.Writeline(a+b);
}
五,关于线程同步(synchronization)和线程异步(Asynchronous)的理解

官方解释:

线程同步:编写线程应用程序时,可能需要使各单个线程与程序的其他部分同步。同步可以在多线程编程的非结构化特性与同步处理的结构化顺序之间进行平衡。

同步技术具有以下用途:

  • 如果必须以特定顺序执行任务,使用同步技术可以显式控制代码运行的顺序。

  • 使用同步技术可以避免当两个线程同时共享同一资源时可能会发生的问题。

  • 例如,使用同步技术可以使显示过程等待到在另一个线程上运行的数据检索过程完成后再运行

  • 如果和异步可以同时选择的话,请选择异步

  • 如图:


个人理解:同步线程即多个线程(如A,B,C)不管是调用还是执行中,必须按照顺序执行如:当A在执行时,B和C等待或休眠中,什么也不做。A执行完毕,发出信号,B收到信号就可以开始执行了,B执行完发出信号,C接着执行。这样的好处是,当三个线程访问或执行的是同一段代码资源、数据的时候,避免不同步的修改。坏处是访问三个不同的资源,相互之间不需要相互影响的信息,按顺序执行,是效率非常低的方式。

线程中配合同步线程的信号:

1. t.join();

2. AutoResetEvent 

3. ManualResetEvent

4. 自定义信号变量

Summary 总结:同步线程操作,使用简单容易理解,轻松解决访问同一资源的情况,但执行效率很低下

线程异步:

多个线程,除了起点不同有细微的时间差,在执行中是可以同时在发生的,互不干扰,效率较高,使用最多。

异步需要注意的一个问题:

线程的异步特性意味着必须协调对资源(如文件句柄、网络连接和内存)的访问。 否则,两个或更多的线程可能在同一

时间访问相同的资源,而每个线程都不知道其他线程的操作。 结果将产生不可预知的数据损坏。

官方解释j解决方案:

    在应用程序中使用多个线程的一个好处是每个线程都可以异步执行。 对于 Windows 应用程序,耗时的任务可以在后台执行,而使应用程序窗口和控件保持响应。 对于服务器应用程序,多线程处理提供了用不同线程处理每个传入请求的能力。 否则,在完全满足前一个请求之前,将无法处理每个新请求。

     使用锁或监视器对于防止同时执行区分线程的代码块很有用,但是这些构造不允许一个线程向另一个线程传达事件。 这需要“同步事件”,它是有两个状态(终止和非终止)的对象,可以用来激活和挂起线程。 让线程等待非终止的同步事件可以将线程挂起,将事件状态更改为终止可以将线程激活。 如果线程尝试等待已经终止的事件,则线程将继续执行,而不会延迟。
     同步事件有两种:AutoResetEvent 和 ManualResetEvent。 它们之间唯一的不同在于,无论何时,只要 AutoResetEvent 激活线程,它的状态将自动从终止变为非终止。 相反,ManualResetEvent 允许它的终止状态激活任意多个线程,只有当它的 Reset 方法被调用时才还原到非终止状态。
可以通过调用 WaitOne、WaitAny 或 WaitAll 等中的某个等待方法使线程等待事件。 WaitHandle.WaitOne 使线程一直等待,直到单个事件变为终止状态;WaitHandle.WaitAny 阻止线程,直到一个或多个指示的事件变为终止状态;WaitHandle.WaitAll 阻止线程,直到所有指示的事件都变为终止状态。 当调用事件的 Set 方法时,事件将变为终止状态。

----------------------------------------------------------------------------------------------------------------------------------

所以,不管是同步还是异步,都面临两种情况:

1. 多个线程访问不同代码段,不同的资源。【果断用异步】2. 多个线程访问同一个资源代码段。【此时要考虑,同步执行,使用

1. t.join();

2. AutoResetEvent 

3. ManualResetEvent

4. 自定义信号变量

,或者异步线程情况下把资源(最好是设为私有)加上Lock】 下面的例子是 主线程用AutoResetEvent等待子线程处理之后再执行的案例:
 public static AutoResetEvent wait = new AutoResetEvent(false);
 static void Main(string[] args)
        { 
            synchroThread Testsynchro = new synchroThread();
            Testsynchro.CreatThread();//调用类中创建线程的方法
            Console.WriteLine("主线程等待,前台线程中。。。");
            wait.WaitOne();//等待其他线程结束.........................................
            //WaitHandle.WaitAll()//等待所有具有set的线程结束

            Thread Program_T = Thread.CurrentThread;
            Console.WriteLine(Program_T.ThreadState);
            Console.WriteLine("当前线程编号:"+Thread.CurrentThread.ManagedThreadId+"   主线程结束");
           
            //synchroThread.loopSwitch = false;//该状态变量是让子线程跟随主线程一起结束
        }    
//------------------------------------------------------------------------------
  public void CreatThread()
        {
            
            ThreadStart T_start1 = new ThreadStart(DoloadSence);
            Thread T_01 = new Thread(T_start1);
            T_01.Start();
            T_01.Name = "Thread_01";
        }
        public void DoloadSence()
        {
            double A = 0;
            do
            {
                for (int i = 0; i < 1000; i++)
                {
                   A+=i;
                    Console.WriteLine(A);
                }
            } while (false);
            Console.WriteLine("线程名称;"+Thread.CurrentThread.Name+"计算结果:"+A+"告诉主线程当前子线程结束");
            Program.wait.Set();//告诉主线程我已经结束,写在需要被等待的线程中
        }

    }//end class synchroThread

再次利用AutoResetEvent 的小例子:
多个线程访问同一数据时候,怎么调控子线程和子线程之间的关系?
class TestWaitone//三个线程同时访问一个函数,利用AutoResetEvent的waitone,实现三个线程按顺序执行
    {
        public static AutoResetEvent wait_01 = new AutoResetEvent(false);    
        public void activeThread()
        {
            Thread T_01 = new Thread(new ThreadStart(count));
            T_01.Name = "T_01";
            T_01.Start();
            wait_01.WaitOne();//当主线程一边执行 T_01.Start();一边往下执行语句的时候,给予一个WaitOne(),告诉主程序等待
一下,知道收到一个可以执行的信号,信号从被等待的那个,程序中发出也就是wait_01.Set();
            Thread T_02 = new Thread(new ThreadStart(count));
            T_02.Name = "T_02";
            T_02.Start();
            wait_01.WaitOne();
            Thread T_03 = new Thread(new ThreadStart(count));
            T_03.Name = "T_03";
            T_03.Start();
        }
        private void count()
        {
           
            for (int i = 0; i < 1000; i++)
            {
                Console.WriteLine(Thread.CurrentThread.Name+"--"+i);               
            }
            Console.WriteLine("子线程"+ Thread.CurrentThread.Name+"已执行完毕!线程ID:"+ 


Thread.CurrentThread.ManagedThreadId);
            wait_01.Set();//发出执行结束信号
        }

    }


官方一个例子,主线程让子线程暂时挂起等待,知道主线程完成了要做的事件
 using System;
    using System.Threading;
    class ThreadingExample
    {
        static AutoResetEvent autoEvent;
        static void DoWork()//子函数
        {
            Console.WriteLine("   worker thread started, now waiting on event...");
            autoEvent.WaitOne();//子线程暂时挂起
            Console.WriteLine("   worker thread reactivated, now exiting...");
        }
        static void Main()//主函数
        {
            autoEvent = new AutoResetEvent(false);
            Console.WriteLine("main thread starting worker thread...");
            Thread t = new Thread(DoWork);
            t.Start();
            Console.WriteLine("main thread sleeping for 1 second...");
            Thread.Sleep(1000);
            Console.WriteLine("main thread signaling worker thread...");
            autoEvent.Set();//通知子线程可以开始了
        }
    }
但是同时访问资源的首选技术是lock。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
六、关于Lock来保护多线程访问同一资源时候的安全方法之一
 官方的一些解释:
lock (C#) 或 SyncLock (Visual Basic) 关键字通常比直接使用 Monitor 类更可取,一方面是因为 lock 或 SyncLock 更简洁,另
一方面是因为 lock 或 SyncLock 确保了即使受保护的代码引发异常,也可以释放基础监视器。 这是通过 finally 关键字来实现
的,无论是否引发异常它都执行关联的代码块。
public class TestThreading
    {
        private System.Object lockThis = new System.Object();
        
        public void Process()
        {
            
            lock (lockThis)
            {
                // Access thread-sensitive resources.
            }
        }
        
    }
提供给 lock 关键字的参数必须为基于引用类型的对象,该对象用来定义锁的范围。 在上面的示例中,锁的范围限定为此函数,因为函数外不存在任何对对象 lockThis 的引用。 如果确实存在此类引用,锁的范围将扩展到该对象。 严格地说,提供的对象只是用来唯一地标识由多个线程共享的资源,所以它可以是任意类实例。 然而,实际上,此对象通常表示需要进行线程同步的资源。 例如,如果一个容器对象将被多个线程使用,则可以将该容器传递给 lock,而 lock 后面的同步代码块将访问该容器。 只要其他线程在访问该容器前先锁定该容器,则对该对象的访问将是安全同步的。
   通常,最好避免锁定 public 类型或锁定不受应用程序控制的对象实例。 例如,如果该实例可以被公开访问,则 lock(this) 可能会有问题,因为不受控制的代码也可能会锁定该对象。 这可能导致死锁,即两个或更多个线程等待释放同一对象。 出于同样的原因,锁定公共数据类型(相比于对象)也可能导致问题。 锁定字符串尤其危险,因为字符串被公共语言运行时 (CLR)“暂留”。 这意味着整个程序中任何给定字符串都只有一个实例,就是这同一个对象表示了所有运行的应用程序域的所有线程中的该文本。 因此,只要在应用程序进程中的任何位置处具有相同内容的字符串上放置了锁,就将锁定应用程序中该字符串的所有实例  。
    因此,最好锁定不会被暂留的私有或受保护成员。 某些类提供专门用于锁定的成员。 例如,Array 类型提供 SyncRoot。 许
多集合类型也提供 SyncRoot。

lock (x)
            {
                DoSomething();
            }
这等效于:
C#VB
            System.Object obj = (System.Object)x;
            System.Threading.Monitor.Enter(obj);
            try
            {
                DoSomething();
            }
            finally
            {
                System.Threading.Monitor.Exit(obj);
            }
lock 关键字可确保当一个线程位于代码的临界区时,另一个线程不会进入该临界区。 如果其他线程尝试进入锁定的代码,则它
将一直等待(即被阻止),直到该对象被释放。
线程处理(C# 和 Visual Basic) 这节讨论了线程处理。
lock 关键字在块的开始处调用 Enter,而在块的结尾处调用 Exit。 ThreadInterruptedException 引发,如果 Interrupt 中断等待
输入 lock 语句的线程。
通常,应避免锁定 public 类型,否则实例将超出代码的控制范围。 常见的结构 lock (this)、lock (typeof (MyType)) 和 lock 
("myLock") 违反此准则:
如果实例可以被公共访问,将出现 lock (this) 问题。
如果 MyType 可以被公共访问,将出现 lock (typeof (MyType)) 问题。
由于进程中使用同一字符串的任何其他代码都将共享同一个锁,所以出现 lock("myLock") 问题。
最佳做法是定义 private 对象来锁定, 或 private static 对象变量来保护所有实例所共有的数据。

在 lock 语句的正文不能使用 等待 关键字。(You can't use the await keyword in the body of a lock statement.)

官方的一个例子,稍微修改了:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;


namespace synchronyThreadLock
{
    class Program
    {
        static void Main()
        {
            Thread[] threads = new Thread[10];
            Account acc = new Account(1000);//报一下余额
            for (int i = 0; i < 10; i++)
            {
                Thread t = new Thread(new ThreadStart(acc.DoTransactions));//开始让10个线程“同时”绑定你的银行卡账户
                threads[i] = t;
            }
            for (int i = 0; i < 10; i++)
            {
                threads[i].Start();//“同时”开始访问
            }
        }
    }
}
class Account
{
    private Object thisLock = new Object();
    int balance;//默认是私有的,通过构造函数来赋值的
    Random r = new Random();
    public Account(int initial)//构造函数
    {
        balance = initial;
    }


   private int Withdraw(int amount)//此函数是私有的
    {
        #region
        // This condition never is true unless the lock statement
        // is commented out.
        if (balance < 0)
        {
            throw new Exception("Negative Balance");
        }
        #endregion
        // Comment out the next line to see the effect of leaving out 
        // the lock keyword.
        lock (thisLock)//锁定,一次只允许一个线程访问
        {
            if (balance >= amount)
            {
                Console.WriteLine("余额充足");
                Console.WriteLine("当前余额 :  " + balance+"元");
                Console.WriteLine("本次支出 : -" + amount+"元");
                balance = balance - amount;
                Console.WriteLine("消费之后的余额  :  " + balance+"元");
                Console.WriteLine("--------------------------");
                return amount;
            }
            else
            {
                return 0; // transaction rejected
            }
        }
    }
    public void DoTransactions()//随机花费的总额
    {
        for (int i = 0; i < 100; i++)
        {
            Withdraw(r.Next(1, 100));
        }
    }
}
输出;
本次支出 : -60元
消费之后的余额  :  298元
--------------------------
余额充足
当前余额 :  298元
本次支出 : -2元
消费之后的余额  :  296元
--------------------------
余额充足
当前余额 :  296元
本次支出 : -80元
消费之后的余额  :  216元
--------------------------
余额充足
当前余额 :  216元
本次支出 : -45元
消费之后的余额  :  171元
--------------------------
余额充足
当前余额 :  171元
本次支出 : -94元
消费之后的余额  :  77元
--------------------------
余额充足
当前余额 :  77元
本次支出 : -77元
消费之后的余额  :  0元
--------------------------

你可能感兴趣的:(Unity与c#)