多线程和异步编程

进程、应用程序域、上下文及线程之间的关系:
  进程:用来描述一组资源和程序运行所必须的内存分配,简单来说就是一个运行程序。
  线程:进程(可执行应用程序)中的基本执行单元
  应用程序域:承载.net可执行程序的逻辑分区
  上下文:应用程序域可以被划分为多个上下文边界
 
 1.一个.net进程可以承载多个应用程序域.每一个应用程序域可以承载多个相关的.net程序集
 2.一个给定的应用程序域中包含一个或多个上下文。使用上下文CLR能将‘有特殊需求’的队象
   放置到一个逻辑容器中,确保该对象的运行时需求能被满足。
 3.一个应用程序域中可能有多个线程,而且一个特定的线程在其生命周期内并不一定被限制在
   一个应用程序域中(线程调度使其可跨越应用程序域的边界)
 4.在特定的时刻,一个线程也可以移动到一个特定的上下文中
 
委托的异步性:
    定义一个委托:public delegate int DelCalculate(int x, int y);
    通过IL反编译工具可以看到委托的方法结果有以下三个:

    1.Invoke()方法用来调用被代理的对象以同步方式维护的方法
    2.BeginInvoke()用于异步调用方法,返回的对象实现了IAsyncResult接口
    3.EndInvoke()用于获取被调用方法的返回值,需要IAsyncResult类型作为参数

 在研究委托的异步方法前,现来看一下同步调用方法的实现
    同步调用方法: 
 
     public delegate int DelCalculate(int x, int y);
        static void Main(string[] args)
        {
            Console.WriteLine("***** Synch Delegate Review *****");
            //输出正在执行线程ID
            Console.WriteLine("Main() invoked on thread {0}.",Thread.CurrentThread.ManagedThreadId);
            //同步模式下调用Add()方法
            DelCalculate cal = new DelCalculate(Add);
            int result = cal.Invoke(5, 5);
            //Add方法执行完才会执行下面的代码
            Console.WriteLine("Doing more work in Main()");
            Console.WriteLine("result is {0}",result);
            Console.ReadLine();
        }

 

        private static int Add(int x, int y)
        {
            //输出正在执行线程ID
            Console.WriteLine("Add() invoked on thread {0}.", Thread.CurrentThread.ManagedThreadId);
            //模拟一个耗时操作
            Thread.Sleep(5000);
            return x + y;
        }
 
    输出结果为:

    ***** Synch Delegate Review *****
    Main() invoked on thread 1
    Add() invoked on thread 1
    Doing more work in Main()
    result is 10
  
  可以看到线程ID是一样的(所有的任务都由主线程完成),并且执行Add方法时会有5秒的停顿
  
 异步调用方法:
 
     static void Main(string[] args)
        {
            Console.WriteLine("***** Synch Delegate Review *****");
      //输出正在执行线程ID
            Console.WriteLine("Main() invoked on thread {0}.",Thread.CurrentThread.ManagedThreadId);
      //在次线程中调用ADD方法
            DelCalculate cal = new DelCalculate(Add);
      //BeginInvoke有4个参数,前两个要与ADD方法匹配,后两个可为空
            IAsyncResult iftAR = cal.BeginInvoke(5, 5,null,null);
      //主线程做其他事情
            Console.WriteLine("Doing more work in Main()");
   
      //获取ADD方法的结果
            int result = cal.EndInvoke(iftAR);
            Console.WriteLine("result is {0}",result);
            Console.ReadLine();
        }

  输出结果为:
  
      ***** Synch Delegate Review *****
      Main() invoked on thread 1
    Doing more work in Main()
      Add() invoked on thread 3
      result is 10
   
  除了ID不同外,只要程序一运行消息 Doing more work in Main()会立刻显示出来。
  存在的问题:主线程输出 Doing more work in Main() 消息后还是会处于阻塞状态直到次线程方法全部完成
  
  a.IAsyncResult接口提供了IsCompleted属性,可以通过这个属性来判断异步调用(即次线程)是否完成
             

           DelCalculate cal = new DelCalculate(Add);

      IAsyncResult iftAR = cal.BeginInvoke(5, 5,null,null);
   
            //在ADD方法完成之前会一直输出
            while (!iftAR.IsCompleted)
            {
                Console.WriteLine("Doing more work in Main()");
                Thread.Sleep(1000);
            }

      int result = cal.EndInvoke(iftAR);           

     Console.WriteLine("result is {0}",result);   

           
   -------------------------------------------
   除了IsCompleted属性外,IAsyncResult接口提供了AsyncWaitHandle属性,该属性返回一个WaitHandle类
   实例,该实例公开了一个WaitOne()方法(可以指定等待时间),使用该方法可以实现更加灵活的等待逻辑
   
            while (!iftAR.AsyncWaitHandle.WaitOne(1000,true))
            {
                Console.WriteLine("Doing more work in Main()");
            }
   -------------------------------------------
   
  b.上面的方式还是不够高效,IsCompleted属性相当于主线程一直在询问次线程是否有执行完。 BeginInvoke
    方法提供了一个AsyncCallback委托的实例作为参数(就是第三个参数),当提供了这个对象时,异步调用
    完成时,委托会自动调用AsyncCallback对象所指定的方法
    
 

      private static bool isDone = false;
      static void Main(string[] args)       

      {           

       Console.WriteLine("***** Synch Delegate Review *****");           

            Console.WriteLine("Main() invoked on thread {0}.",Thread.CurrentThread.ManagedThreadId);   

       DelCalculate cal = new DelCalculate(Add);
            //传入了第三个参数一个AsyncCallback委托的实例
            IAsyncResult iftAR = cal.BeginInvoke(5, 5,new AsyncCallback(AddComplete),null);
      //这里主线程做其他事情
            while (!isDone)
            {
                Console.WriteLine("Doing more work in Main()");
                Thread.Sleep(1000);
            }

            Console.ReadLine();
        }

        private static int Add(int x, int y)
        {
            Console.WriteLine("Add() invoked on thread {0}.", Thread.CurrentThread.ManagedThreadId);
            Thread.Sleep(5000);
            return x + y;
        }

        static void AddComplete(IAsyncResult itfAR)
        {
            Console.WriteLine("AddComplete() invoked on thread {0}.", Thread.CurrentThread.ManagedThreadId);
            Console.WriteLine("Your addition is complete");
      //AsyncCallback委托的目标无法访问Main()中创建的委托,故此处需要强转
      //AsyncResult类继承了IAsyncResult接口,并且AsyncResult类的只读属性AsyncDelegate返回了别处创建的原始异步委托的调用
            AsyncResult ar = (AsyncResult)itfAR;
      //(DelCalculate)ar.AsyncDelegate返回的就是Main()中创建的委托
            DelCalculate cal = (DelCalculate)ar.AsyncDelegate;
            Console.WriteLine("result is {0}.",cal.EndInvoke(itfAR));

      isDone = true;       

  }  

           
  c.BeginInvoke()方法的最后一个参数(object类型)允许主线程传递额外的状态信息给回调方法
  
      IAsyncResult iftAR = cal.BeginInvoke(5, 5,new AsyncCallback(AddComplete),"calculate is over");
   
      static void AddComplete(IAsyncResult itfAR)
            {
        //通过AsyncResult类的只读属性AsyncState获取传入的数据
        string message = (string)itfAR.AsyncState;
        Console.WriteLine(message);

                  isDone = true;
            }
   
   
多线程: 
 手动创建次线程的步骤:
 1.创建一个方法作为线程的入口点
 2.创建一个ThreadStart/ParameterizedThreadStart委托,并把上一步所定义方法的地址传递给委托的构造函数
 3.创建一个Thread对象,并把ThreadStart/ParameterizedThreadStart委托作为构造函数的参数
 4.建立任意初始化线程的特性(名称、优先级等)
 5.调用Thread.Start()方法
 
 使用ThreadStart委托:该委托仅仅指向无返回值、无参数的方法
 
    static void Main(string[] args)
         {
            Thread primaryThread = Thread.CurrentThread;
            primaryThread.Name = "Primary";

            Console.WriteLine("-->{0} is executing Main()", Thread.CurrentThread.Name);

            Printer p = new Printer();
       //第二步创建一个ThreadStart委托
            ThreadStart start = new ThreadStart(p.PrintNumbers);
       //第三步创建一个Thread对象
            Thread newThread = new Thread(start);
      //第四步
            newThread.Name = "Secondary";
       //第五步
            newThread.Start();
           
            MessageBox.Show("I am busy!", "Work on main thread..");
        }
  
    public class Printer
        {
       public void PrintNumbers() //第一步创建一个方法作为线程的入口点
       {
          Console.WriteLine("-->{0} is executing PrintNumbers()",Thread.CurrentThread.Name);
          Console.WriteLine("Your Numbers: ");
          for (int i = 0; i < 10; i++)
          {
             Console.WriteLine("{0}, ",i);
             Thread.Sleep(2000);
          }
          Console.WriteLine();
       }
    }
  
 使用ParameterizedThreadStart委托:接受一个Object类型参数,无返回值
 
            Printer p = new Printer();
            ParameterizedThreadStart start = new ParameterizedThreadStart(p.PrintNumbers);
            Thread newThread = new Thread(start);
            newThread.Start(10);
            newThread.Name = "Secondary";
            MessageBox.Show("I am ParameterizedThreadStart!");
   
       public void PrintNumbers(object num)
            {
        Console.WriteLine("-->{0} is executing PrintNumbers()", Thread.CurrentThread.Name);
        Console.WriteLine("Your Numbers: ");
        //出于测试目的此处强转不严谨
        for (int i = 0; i <(int)num; i++)
        {
           Console.WriteLine("{0}, ", i);
           Thread.Sleep(2000);
        }
        Console.WriteLine();
           }

前台线程:能阻止应用程序的终结,直到所有的前台线程都终止后,CLR才能关闭应用程序

后台线程:被CLR认为是程序执行中可作出牺牲的部分,即在任何时候都可以被忽略 

并发问题:在构建多线程应用程序时,需要确保任何共享数据都处于被保护状态,以防止多个线程修改他的值
                (当有两个线程访问同一共享数据时,由于线程调度器会随时挂起线程,所以如果A正在修改共享
                  数据但还没有完成,这时B又来访问此时A被挂起,由于A还没有完成所以此时B访问的就是错误的数据) 

 1.使用C#的Lock关键字进行同步(首先方式)
    public class Printer
    {
        private object threadLock = new object();
        public void PrintNumbers()
        {
            //如果没有Lock那么当有多个线程同时访问此方法时输出的可能就是不规则的数
            lock(threadLock)
            {
                Console.WriteLine("-->{0} is executing PrintNumbers()", Thread.CurrentThread.Name);
                Console.WriteLine("Your Numbers: ");
                for (int i = 0; i < 10; i++)
                {
                    Console.WriteLine("{0}, ", i);
                    Thread.Sleep(1000);
                }
                Console.WriteLine();
            }
        }
    }
 
 Main函数中如下:
 


       Printer p = new Printer();
       //创建10个线程使其都访问p.PrintNumbers()方法
            Thread[] threads = new Thread[10];
            for (int i = 0; i < 10; i++)
            {
                threads[i] = new Thread(new ThreadStart(p.PrintNumbers));
                threads[i].Name = string.Format("Worker thread #{0}", i);
            }

   foreach (Thread item in threads)            {                item.Start();            }   

     2.使用System.Threading.Monitor类型进行同步
 
      public class Printer
      {
        private object threadLock = new object();
        public void PrintNumbers()
        {
            Monitor.Enter(threadLock);
             try
            {
                Console.WriteLine("-->{0} is executing PrintNumbers()", Thread.CurrentThread.Name);
                Console.WriteLine("Your Numbers: ");
                for (int i = 0; i < 10; i++)
                {
                    Console.WriteLine("{0}, ", i);
                    Thread.Sleep(1000);
                }
                Console.WriteLine();
            }
            finally
            {
                Monitor.Exit(threadLock);
            }
        }
      }

 

3.使用[Synchronization]特性进行同步(类级别特性)
 


    //此时Printer类中所有的方法都是线程安全的
    [Synchronization]
    public class Printer:ContextBoundObject  //必须继承ContextBoundObject类
    {
        private object threadLock = new object();
        public void PrintNumbers()
        {
           
                Console.WriteLine("-->{0} is executing PrintNumbers()", Thread.CurrentThread.Name);
                Console.WriteLine("Your Numbers: ");
                for (int i = 0; i < 10; i++)
                {
                    Console.WriteLine("{0}, ", i);
                    Thread.Sleep(1000);
                }
                Console.WriteLine();
         }
     }

 
C#中的async和await关键字

 先创建一个Windows Form应用程序,在窗体上添加一个Button和TextBox控件,代码如下
 


   private void btnCallMethod_Click(object sender, EventArgs e)
        {
            this.Text = DoWork();
        }

  private string DoWork()       

   {            

      Thread.Sleep(5000);           

      return "Done with work!";      

        }  

       
  当单击了BTN按钮后,需要等待5秒钟才能在TextBox控件中输入文字,主窗体才能够被拖动
  
 使用async和await关键字对上述代码进行修改
 


    private async void btnCallMethod_Click(object sender, EventArgs e)
        {
      //使用async标记过得方法,如果内部没有await方法的调用,则实际上还是阻塞的同步的
            this.Text = await DoWork();  
        }
    //DoWork方法直接返回Task<string>对象(Task.Run()的返回值,该方法接受Func或Action委托)
        private Task<string> DoWork()
        {
            return Task.Run(() =>
            {
                Thread.Sleep(5000);
                return "Done with work!";
            });
         }
  
 上述异步实现也可以通过手动创建多线程来实现,但是用async和await关键字会更简洁
  
 异步方法里可以有多个await方法
 


    private async void btnCallMethod_Click(object sender, EventArgs e)
        {
            this.Text = await DoWork();
      //第二个await方法,还可以有更多个
            await Task.Run(() =>
            {
                Thread.Sleep(5000);
                MessageBox.Show("Hello World1");
            });
        }

        private Task<string> DoWork()
        {
            return Task.Run(() =>
            {
                Thread.Sleep(5000);
                return "Done with work!";
            });


     

----------------------参考学习书籍《精通C#》-------------------


  

posted @ 2017-09-24 23:04  Zed_H  阅读(315)  评论(0编辑  收藏  举报