c#多线程 订阅
C语言是一门面向过程的、抽象化的通用程序设计语言,广泛应用于底层开发。C语言能以简易的方式编译、处理低级存储器。C语言是仅产生少量的机器语言以及不需要任何运行环境支持便能运行的高效率程序设计语言。尽管C语言提供了许多低级处理的功能,但仍然保持着跨平台的特性,以一个标准规格写出的C语言程序可在包括类似嵌入式处理器以及超级计算机等作业平台的许多计算机平台上进行编译。 [1] 展开全文
C语言是一门面向过程的、抽象化的通用程序设计语言,广泛应用于底层开发。C语言能以简易的方式编译、处理低级存储器。C语言是仅产生少量的机器语言以及不需要任何运行环境支持便能运行的高效率程序设计语言。尽管C语言提供了许多低级处理的功能,但仍然保持着跨平台的特性,以一个标准规格写出的C语言程序可在包括类似嵌入式处理器以及超级计算机等作业平台的许多计算机平台上进行编译。 [1]
信息
类    别
程序设计语言
影响语言
C++、C#、D、Java、JavaScript、ObjectPHP等
创始时间
1972年
主要编译器
Visual C++、Clang、GCC、Turbo C等
中文名
C语言
创始人
Dennis MacAlistair Ritchie
发    源
BCPL语言
外文名
C Programming Language
c语言简介
C语言是一门面向过程的计算机编程语言,与C++、Java等面向对象编程语言有所不同。C语言的设计目标是提供一种能以简易的方式编译、处理低级存储器、仅产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言。C语言描述问题比汇编语言迅速、工作量小、可读性好、易于调试、修改和移植,而代码质量与汇编语言相当。C语言一般只比汇编语言代码生成的目标程序效率低10%~20%。因此,C语言可以编写系统软件。 [2]  当前阶段,在编程领域中,C语言的运用非常之多,它兼顾了高级语言和汇编语言的优点,相较于其它编程语言具有较大优势。计算机系统设计以及应用程序编写是C语言应用的两大领域。同时,C语言的普适较强,在许多计算机操作系统中都能够得到适用,且效率显著。 [3]  冯.诺依曼在1945年提出了现代计算机的若干思想,被后人称为冯.诺依曼思想,这是计算机发展史上的里程碑。自1945 年至今大多采用其结构,因此冯.诺依曼被称为计算机之父。他的体系结构计算机由运算器、控制器、存储器、输入设备、输出设备五大部件构成。 C语言拥有一套完整的理论体系经过了漫长的发展历史,在编程语言中具有举足轻重的地位。 [4] 
收起全文
精华内容
下载资源
问答
  • C#多线程

    千次阅读 2016-03-30 19:10:43
    高手讲解C#多线程揭秘教程 www.111cn.net 编辑:edit02_lz 来源:转载 本教程是高手整理的C#多线程详解,内容将通过一些实例来说明.net中如何实现多线程,主要内容有:线程概念、如何实现多线程、如何确保线程...

    高手讲解C#多线程揭秘教程

    www.111cn.net 编辑:edit02_lz 来源:转载
    本教程是高手整理的C#多线程详解,内容将通过一些实例来说明.net中如何实现多线程,主要内容有:线程概念、如何实现多线程、如何确保线程安全、死锁

    什么是进程

    一个进程就是一个可执行文件运行的操作系统上下文环境。它被用来分隔虚拟地址空间,线程,对象句柄(指向类似文件这样的资源的指针),以及环境变量,进程还有一些类似优先级类和最大内存分配的属性。

    也就是说:
    1 .一个进程就是一个包含资源的内存块。
    2 .操作系统执行的一个单独的任务。
    3 .一个正在运行的软件
    4 .一个进程拥有一个/多个操作系统线程

    一般的。一个进程最大可以是4GB的内存空间,这块内存是安全,私有,其他进程是无法访问的。

    什么是线程

    一个线程就是在一个进程里执行的一条指令流,所有的线程都在一个进程里执行,也就是一个进程可以包含多个线程。线程公用进程的虚拟地址空间。线程是操作系统的调度单元。一个线程的上下文由操作系统进行保存/恢复。
    也就是说:
    1 .一个线程是进程里的一条指令流。
    2 .所有的线程在进程里。一个进程可以有多个线程
    3 .一个进程的所有线程使用进程的虚拟地址空间。

    什么是多线程

    多线程指的是进程同时有多个线程活动。这可以通过时间片的线程模拟或是多cpu上的超线程来实现。可以提高性能。
    多线程-为什么或是为什么不?

    为什么多线程

    1 .保持UI响应。
    2 .提高性能(对于cpu密集型和I/O密集型的进程)
    为什么不多线程
    1 .过度使用降低性能
    2 .代码复杂,增加设计时间,潜在的bug

    线程池

    线程池为你的程序提供了一个由操作系统管理的机制。在线程池里的都是后台线程。一个线程池线程在程序的前台线程都退出后,也会推出。每个进程一个线程池。默认情况下。每个处理器会为进程分配25个线程。但是可以通过SetMaxThreads 方法来改变。

    .net 中的线程

    在.net 中,线程可以通过下面6个方法来实现。
    1 .Thread线程类
    2 .Delegates委托
    3 .Background Worker
    4 .ThreadPool 线程池
    5 .Task任务类
    6 .Parallel并行类

    下面的几部分里。我将逐一展示实现方法。

    简而言之,多线程就是通过使程序同时运行多个任务来最大化计算机能力,同时能够保持UI响应。下图是一个例子的图示。

    高手讲解C#多线程揭秘教程

    代码


    提供的源码是一个简单的WinForm程序。模拟了.net中委托,线程类和Background Worker三种方法。
    程序异步执行一个繁重的操作,这样UI就不会无响应。三个方法都是模拟的。

    高手讲解C#多线程揭秘教程

    这个“繁重”的操作
    真实的开发中,这个繁重的操作从轮询数据库到流媒体操作都可以。基本上可以是任何事情。源码里面是向一个字符串追加值。String是不能变的。追加的时候,新的字符串变量会被创建,旧的会被丢弃,这是由CLR处理的。如果做很多次这个操作,是很耗资源的。这也是为什么我们使用Stringbuilder.Append 来代替这个操作。通过调整界面中的次数。可以通知追加的次数。

    后面我们有一个Utility泪,有一个LoadData() 方法。类里面也有一个和LoadData() 有着同样签名的委托

    class Utility
    {
        public delegate string delLoadData(int number);
        public static delLoadData dLoadData;

        public Utility()
        {

        }

        public static string LoadData(int max)
        {
            string str = string.Empty;

            for (int i = 0; i < max; i++)
                                    {
                str += i.ToString();
                                    }

            return str;
        }
    }

    同步调用

    当点击Get Data Sync按钮的时候。操作和UI在同一个线程里,因此阻塞了UI线程。因此。UI线程会未响应

    private void btnSync_Click(object sender, EventArgs e)
    {
        this.Cursor = Cursors.WaitCursor;
        this.txtContents.Text = Utility.LoadData(upCount);
        this.Cursor = Cursors.Default;
    }

    异步调用


    使用委托(异步编程模型)

    如果你选择了“Delegates”单选按钮,那么LoadData()方法就会通过使用委托来异步调用。首先通过utility.LoadData(). 的地址初始化delLoadData 类型,然后调用委托的BeginInvoke()方法。在.net的世界里。任何一个有着BeginXXX和EndXXX名字的方法都是异步的。比如delegate.Invoke()将会在同一个线程里调用方法。而delegate.BeginInvoke()则会另开一个线程调用。
    BeginInvoke()有三个参数
    1 .传递给Utility.LoadData()方法的参数
    2 .回调方法的地址
    3 .对象的状态

    Utility.dLoadData = new Utility.delLoadData(Utility.LoadData);
    Utility.dLoadData.BeginInvoke(upCount, CallBack, null);

    回调

    一旦我们开了一个线程执行一些操作,我们就想知道操作正在发生些什么,换句话说。我们需要当操作完成的时候我们能够收到通知。有三种方法可以知道一个操作是否完成。
    1 .回调
    2 .轮询
    3 .等待直到完成
    在我的源码里,我们使用回调方法来捕获线程的完成。回调只需要在调用BeginInvoke的时候把回到函数的名字传递进去。这会告诉线程当你做完工作以后调用这个回调方法就好了。

    一旦一个独立线程里的一个方法启动。你也许关心也许不关心方法的返回值,如果一个方法没有返回值,那么可以叫做“触发然后忘记的调用”,这种情况下就不需要回调函数了。这里callback直接传入null就可以了。

    Utility.dLoadData.BeginInvoke(upCount, CallBack, null);

    在我们的例子中,我们需要一个回调方法,因此,哦们需要传递回调方法的名字到参数里。这里我们的回调方法的名字就叫做CallBack(),纯属巧合。

    private void CallBack(IAsyncResult asyncResult)
    {
        string result= string.Empty;

        if (this.cancelled)
            result = "Operation Cancelled";
        else
            result = Utility.dLoadData.EndInvoke(asyncResult);

          object[] args = { this.cancelled, result };
        this.BeginInvoke(dUpdateUI, args);
    }

    回调方法的签名都是void MethodName(IAsyncResult asyncResult).
    IAsyncResult包含了关于线程的一些必要的信息,返回的数据可以像下面这样提取。

    result = Utility.dLoadData.EndInvoke(asyncResult);

    而轮询的方法(本例没有使用)则是像这样

    IAsyncResult r = Utility.dLoadData.BeginInvoke(upCount, CallBack, null);
    while (!r.IsCompleted)
    {
        //do work
    }
    result = Utility.dLoadData.EndInvoke(asyncResult);

    等待直到完成,如名所示,就是等待直到完成。

    IAsyncResult r = Utility.dLoadData.BeginInvoke(upCount, CallBack, null);

    //do work
    result = Utility.dLoadData.EndInvoke(asyncResult);

    更新UI
    既然我们已经捕获了操作结束,并且取回了LoadData()的结果。我们需要用结果来更新UI,但是有个问题。文本框需要在UI线程里更新,结果在回调里取到了。回调和他启动的时候是一个线程(他是由新开的线程启动的)。因为UI线程和回调不是同一个线程。换句话说。文本框不能像下面这样更新。

    this.txtContents.Text = text;

    回调里执行这一行将会导致一个跨线程的系统异常。我们需要在后台线程和Ui线程之前构建一个桥。来更新文本框的值。可以通过使用Invoke()或是BeginInvoke()方法。
    我定义了一个方法来更新UI

    private void UpdateUI(bool cancelled, string text)
    {
        this.btnAsync.Enabled = true;
        this.btnCancel.Enabled = false;
        this.txtContents.Text = text;
    }

    对上面的方法定义一个委托

    private delegate void delUpdateUI(bool value, string text);
    dUpdateUI = new delUpdateUI(UpdateUI);

    如下调用BeginInvoke()方法。

    object[] args = { this.cancelled, result };
    this.BeginInvoke(dUpdateUI, args);

    需要注意的是一旦一个线程通过委托启动。它就不能取消,暂停,或是终止,我们无法控制那个线程。

    使用Thread线程类
    同样的操作可以是哟哦那个Thread类来完成。这个类的优点是你可以对操作有更多的控制,比如暂停/取消操作,类在System.Threading命名空间里。
    我们有一个私有的方法LoadData(),他是Utility.LoadData()方法的一个包装。

    private void LoadData()
    {
        string result = Utility.LoadData(upCount);
        object[] args = { this.cancelled, result };
        this.BeginInvoke(dUpdateUI, args);
    }

    这样做是因为 Utility.LoadData() 需要一个参数。而我们需要一个ThreadStart委托,这个委托没有参数。

    doWork = new Thread(new ThreadStart(this.LoadData));
    doWork.Start();

    这个委托没有参数,为了防止我们需要传递参数,我们可以使用有参的ThreadStart委托,不幸的是,这个委托只能把object作为参数,而我们需要一个字符串所以需要类型转换。

    doWork = new Thread(new ParameterizedThreadStart(this.LoadData));
    doWork.Start(parameter);

    是的。Thread泪可以对线程有更多的控制。中断。终止,获取线程状态。
    使用BackgroundWorker
    这个类是一个组件,可以使得线程使用更简单,这个BackgroundWorker类的主要特点就是可以异步的报告进度,这就可以用来更新状态栏,保持UI可视化的更新进度
    为了完成操作,我们需要把下面两个属性设置为true,缺省时false
    • WorkerReportsProgress
    • WorkerSupportsCancel

    这个类有三个主要的事件DoCount, ProgressChanged, RunWorkerCompleted 初始化的时候需要注册这三个事件

    this.bgCount.DoWork += new DoWorkEventHandler(bgCount_DoWork);
    this.bgCount.ProgressChanged += 
         new ProgressChangedEventHandler(bgCount_ProgressChanged);
    this.bgCount.RunWorkerCompleted += 
         new RunWorkerCompletedEventHandler(bgCount_RunWorkerCompleted);

    通过调用RunWorkerAsync() 方法来启动操作

    this.bgCount.RunWorkerAsync();

    一旦调用,下面的方法就会启动来执行操作。

    void bgCount_DoWork(object sender, DoWorkEventArgs e)
    {
        string result = string.Empty;
        if (this.bgCount.CancellationPending)
        {
            e.Cancel = true;
            e.Result = "Operation Cancelled";
        }
        else
        {
            for (int i = 0; i < this.upCount; i++)
            {
                result += i.ToString();
                this.bgCount.ReportProgress((i / this.upCount) * 100);
            }
            e.Result = result;
        }
    }

    CancellationPending 属性用来检查该操作是否被取消。要取消操作,需要调用

    this.bgCount.CancelAsync();

    下面这行代码报告进度

    this.bgCount.ReportProgress((i / this.upCount) * 100);

    一旦调用,下面的方法就会被调用,来更新UI

    void bgCount_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        if (this.bgCount.CancellationPending)
            this.txtContents.Text = "Cancelling....";
        else
            this.progressBar.Value = e.ProgressPercentage;
    }

    最后,操作完成时调用bgCount_RunWorkerCompleted 方法

    void bgCount_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        this.btnAsync.Enabled = true;
        this.btnCancel.Enabled = false;
        this.txtContents.Text = e.Result.ToString();


    Thread Pool线程池

    不建议程序员随心所欲创建很多线程,创建线程是很昂贵的操作,有一些额外的调用。同时,每个cpu在一个时间片内只能运行一个建成,如果一个单核系统上有多个线程,计算机一次只能运行一个,因此通过给线程分配时间片来模拟多线程。会产生上下文切换的消费,因此,如果有很多的线程,其中一些什么都不做,保持闲置,那么这些额外的消费会影响性能。因此,程序员对于创建线程应该相当小心

    幸运的是,CLR有一个托管的代码库。这就是ThreadPool类,这个类管理一些线程,并且根据我们的程序创建或销毁线程,开始的时候没有线程启动。当需要的时候就会创建,如果我们设置了SetMinThreads属性,一旦开始操作就很快会达到这个值,之后,如果发现有些线程闲置了很长时间,则会决定会杀掉一些线程。
    线程池类液允许我们管理一系列的工作项目。这些工作项目会委托到一个后台线程。

    WaitCallback threadCallback = new WaitCallback(HeavyOperation);

    for (int i = 0; i < 3; i++)
    {
      System.Threading.ThreadPool.QueueUserWorkItem(HeavyOperation, i);             
    }

    其中heavyOperation定义如下:

    private static void HeavyOperation(object WorkItem)
    {
      System.Threading.Thread.Sleep(5000);
      Console.WriteLine("Executed work Item {0}", (int)WorkItem);


    注意WaitCallBack这个委托的签名,需要把一个object作为参数,通常用来在线程间传递状态信息。
    注意我们知道委托通过使用ThreadPool来工作。我们必须探索和他一起的回调技术,我们可以使用WaitHandle来捕获回调,WaitHandle派生了两个子类:
    AutoResetEvent 和 ManualResetEvent.

    public static void Demo_ResetEvent()
    {  
      Server s = new Server();
      ThreadPool.QueueUserWorkItem(new WaitCallback((o) =>
      {
         s.DoWork();                

       }));

       ((AutoResetEvent)Global.GetHandle(Handles.AutoResetEvent)).WaitOne();
        Console.WriteLine("Work complete signal received");


    这里有一个Global类,这个类包含WaitHandles的一个单例。

    public static class Global
    {
      static WaitHandle w = null;
      static AutoResetEvent ae = new AutoResetEvent(false);
      static ManualResetEvent me = new ManualResetEvent(false);
      public static WaitHandle GetHandle(Handles Type)
      {            
        switch (Type)
        {                
          case Handles.ManualResetEvent:                    
             w = me;
             break;
          case Handles.AutoResetEvent:                    
             w = ae;                    
             break;
          default:
             break;
        }
        return w;
      }
    }  

    而WaitOne方法。阻塞了代码执行,直到在后台线程中设置了WaitHandle。

    public void DoWork()
    {            
      Console.WriteLine("Work Starting ...");
      Thread.Sleep(5000);
      Console.WriteLine("Work Ended ...");
      ((AutoResetEvent)Global.GetHandle(Handles.AutoResetEvent)).Set();


    AutoResetEvent当自动设置以后又重设自己。和高速收费站很类似。多辆车合并,以让一次只有一辆车通过。当一辆车来的时候,门就设置为允许通过,然后又重设为关闭处理下一辆车。
    下面的例子详细说明了AutoResetEvent。想一想。我们有一个服务名为DoWork()这个方法就是繁重的操作,我们的程序需要在调用这个方法后更新日志文件。考虑到多个线程异步的访问这个方法。我们必须确保更新日志文件是线程安全的,这样一次只能有一个线程可用。

    public void DoWork(int threadID, int waitSingal)

      Thread.Sleep(waitSingal);
      Console.WriteLine("Work Complete by Thread : {0} @ {1}", threadID, DateTime.Now.ToString("hh:mm:ss"));
      ((AutoResetEvent)Global.GetHandle(Handles.AutoResetEvent)).Set();



    public void UpdateLog(int threadID)
    {
      if(((AutoResetEvent)Global.GetHandle(Handles.AutoResetEvent)).WaitOne(5000))
           Console.WriteLine("Update Log File by thread : {0} @ {1}", threadID, DateTime.Now.ToString("hh:mm:ss"));
      else
           Console.WriteLine("Time out");
    }

    我们创建两个线程,同时委托DoWork()方法。然后我们调用UpdateLog()方法。更新日志的代码执行将会等待直到每一个线程都完成各自的工作之后才执行。

    public static void Demo_AutoResetEvent()
    {
      Console.WriteLine("Demo Autoreset event...");
      Server s = new Server();

      Console.WriteLine("Start Thread 1..");
      ThreadPool.QueueUserWorkItem(new WaitCallback((o) =>
      {
         s.DoWork(1, 4000);  

      }));            

      Console.WriteLine("Start Thread 2..");
      ThreadPool.QueueUserWorkItem(new WaitCallback((o) =>
      {
         s.DoWork(2, 4000);                

      }));

      s.UpdateLog(1);
      s.UpdateLog(2);


    ManualResetEvent 不同于AutoResetEvent,我们需要在再次设置之前手工重置他,他不会自动重置,考虑我们有一个方法是持续在后台线程中发布消息。这个方法持续循环等待信号以发送消息。当值被设置以后,方法就开始发消息。当等待句柄被重置,发送服务停止,然后处理就可以重复进行了。

    public void SendMessages(bool monitorSingal)
    {            
      int counter=1;
      while (monitorSingal)
      {
         if (((ManualResetEvent)Global.GetHandle(Handles.ManualResetEvent)).WaitOne())
         {
            Console.WriteLine("Sending message {0}", counter);
            Thread.Sleep(3000);
            counter += 1;
         }
      }           


    public static void Demo_ManualResetEvent()
    {
      Console.WriteLine("Demo Mnaulreset event...");
      Server s = new Server();
      ThreadPool.QueueUserWorkItem(new WaitCallback((o) =>
      {
        s.SendMessages(true);
      }));

      Console.WriteLine("Press 1 to send messages");
      Console.WriteLine("Prress 2 to stop messages");

      while (true)
      {               
        int input = Convert.ToInt16(Console.ReadLine());                              

        switch (input)
        {
          case 1:
             Console.WriteLine("Starting to send message ...");                        
             ((ManualResetEvent)Global.GetHandle(Handles.ManualResetEvent)).Set();
             break;
          case 2:                                                
             ((ManualResetEvent)Global.GetHandle(Handles.ManualResetEvent)).Reset();
             Console.WriteLine("Message Stopped ..."); 
             break;
          default:
             Console.WriteLine("Invalid Input");
             break;
        }
      }            


    任务Task类

    .net 4.0 提出了Task,是ThreadPool的扩展,概念相当美好。我们可以取消任务,等待任务。检查进度,考虑下面的例子将要用到的三个方法。

    static void DoHeavyWork(CancellationToken ct)
    {
     try
     {
                    while (true)
                    {
                        ct.ThrowIfCancellationRequested();
                        Console.WriteLine("Background thread working for task 3..");
                        Thread.Sleep(2000);
                        if (ct.IsCancellationRequested)
                        {
                            ct.ThrowIfCancellationRequested();
                        }
                    }
     }
    catch (OperationCanceledException ex)
        {
                    Console.WriteLine("Exception :" + ex.Message);
           }
     catch (Exception ex)
           {
                    Console.WriteLine("Exception :", ex.Message);
            }            

    }

    static void DoHeavyWork(int n)
    {
      Thread.Sleep(5000);
      Console.WriteLine("Operation complete for thread {0}", Thread.CurrentThread.ManagedThreadId);
    }

    static int DoHeavyWorkWithResult(int num)
    {
      Thread.Sleep(5000);
      Console.WriteLine("Operation complete for thread {0}", Thread.CurrentThread.ManagedThreadId);
      return num;
    }

    我们还有三个task用来运行这三个方法。第一个线程完成没有返回结果,第二个线程完成并且返回结果,第三个线程在完成之前取消。

    try
                {
                    Console.WriteLine(DateTime.Now);
                    CancellationTokenSource cts1 = new CancellationTokenSource();
                    CancellationTokenSource cts2 = new CancellationTokenSource();
                    CancellationTokenSource cts3 = new CancellationTokenSource();

                    Task t1 = new Task((o) => DoHeavyWork(2), cts1.Token);

                    Console.WriteLine("Starting Task 1");
                    Console.WriteLine("Thread1 state {0}", t1.Status);
                    t1.Start();

                    Console.WriteLine("Starting Task 2");
                    Task<int> t2 = Task<int>.Factory.StartNew((o) => DoHeavyWorkWithResult(2), cts2.Token);

                    Console.WriteLine("Starting Task 3");
                    Task t3 = new Task((o) => DoHeavyWork(cts3.Token), cts3);
                    t3.Start();               

                    Console.WriteLine("Thread1 state {0}", t1.Status);
                    Console.WriteLine("Thread2 state {0}", t2.Status);
                    Console.WriteLine("Thread3 state {0}", t3.Status);

                    // wait for task 1 to be over
                    t1.Wait();

                    Console.WriteLine("Task 1 complete");

                    Console.WriteLine("Thread1 state {0}", t1.Status);
                    Console.WriteLine("Thread2 state {0}", t2.Status);
                    Console.WriteLine("Thread3 state {0}", t3.Status);

                    //cancel task 3
                    Console.WriteLine("Task 3 is : {0} and cancelling...", t3.Status);
                    cts3.Cancel();

                    // wait for task 2 to be over
                    t2.Wait();

                    Console.WriteLine("Task 2 complete");

                    Console.WriteLine("Thread1 state {0}", t1.Status);
                    Console.WriteLine("Thread2 state {0}", t2.Status);
                    Console.WriteLine("Thread3 state {0}", t3.Status);

                    Console.WriteLine("Result {0}", t2.Result);
                    Console.WriteLine(DateTime.Now);

                    t3.Wait();

                    Console.WriteLine("Task 3 complete");
                    Console.WriteLine(DateTime.Now);
                }

                catch (Exception ex)
                {
                    Console.WriteLine("Exception : " + ex.Message.ToString());
                }
                finally
                {
                    Console.Read();
                }

    .net 4.0中并行Parallel编程(时间片)
    .net 4.0提出了一个并行编程的很不错的特性,我们前面所说的大部分线程的例子都是把大量的工作交给空闲线程去做。计算机仍然一次处理一个线程。简而言之就是,不是真正的多任务执行,而通过Parallel类这就是可能的。
    考虑一个Employee类,这个类有一个繁重的操作:ProcessEmployeeInformation

    class Employee
    {
      public Employee(){}

      public int EmployeeID {get;set;}

      public void ProcessEmployeeInformation()
      {
        Thread.Sleep(5000);
        Console.WriteLine("Processed Information for Employee {0}",EmployeeID);
      }


    我们创建8个对象,来模拟并行请求,在一个4核的处理器上,4个请求将会同时进行,其余的则会等待。

    List<employee> empList = new List<employee>()
     {
       new Employee(){EmployeeID=1},
       new Employee(){EmployeeID=2},
       new Employee(){EmployeeID=3},
       new Employee(){EmployeeID=4},
       new Employee(){EmployeeID=5},
       new Employee(){EmployeeID=6},
       new Employee(){EmployeeID=7},
       new Employee(){EmployeeID=8},
     };

     Console.WriteLine("Start Operation {0}", DateTime.Now);
     System.Threading.Tasks.Parallel.ForEach(empList, (e) =>e.ProcessEmployeeInformation());

    我们可以通过设置MaxDegreeOfParallelism 的值来控制/限制并行任务的数量。如果被设置为-1,就是说没有限制。。

    System.Threading.Tasks.Parallel.For(0, 8, new ParallelOptions() { MaxDegreeOfParallelism = 4 }, (o) =>
           {
              Thread.Sleep(5000);
              Console.WriteLine("Thread ID - {0}", Thread.CurrentThread.ManagedThreadId);
            });

    并行的问题是如果我们开启了一系列请求,我们不能保持响应也是一样的顺序,顺序是不确定的。而AsOrdered属性可以帮助我们,输入可以是任何顺序,输出就是对应的顺序。

    Console.WriteLine("Start Operation {0}", DateTime.Now);
    var q = from e in empList.AsParallel().AsOrdered()
            select new { ID = e.EmployeeID };

    foreach (var item in q)
    {
      Console.WriteLine(item.ID);
    }
    Console.WriteLine("End Operation {0}", DateTime.Now);

    线程安全

    关于线程常常讨论的一个就是线程安全了。考虑一个被多个线程使用的资源,资源将会以一种不确定的方式被使用,导致结果乱七八糟,这就是我们为什么要实现线程安全的程序,是为了让资源一次只能被一个线程操作,下面是.net中实现线程安全的一些方法。
    Interlocked 这个Interlocked类把操作看作是原子的。比如,简单的加减法在处理器内部是分为三步的。当多个线程访问同样的对象进行这些操作的时候,导致结果混乱,一个建成在执行了前两步后,被挂起。另一个线程执行了完整的三步,之后,当第一个线程恢复执行的时候他就覆写了这个值,第二个线程所做的操作就丢失了。因此我们需要看这些操作看作是原子的。使他们能够线程安全的。比如加减,读,交换等等。
    System.Threading.Interlocked.Increment(object);

    Monitor 这个Monitor类用来锁住那些有可能多线程下有风险的对象。

    if (Monitor.TryEnter(this, 300)) {
        try {
            // code protected by the Monitor here.
        }
        finally {
            Monitor.Exit(this);
        }
    }
    else {
        // Code if the attempt times out.
    }

    Locks 这个Locks类是Monitor的加强版,最好的一个例子就就是单例类的GetInstance() 方法,多个线程可以访问这段代码,因此使用一个syncLock对象锁住,这个对象和真实世界的锁很想,如果两个或多个资源都有要是,他们可以打开锁并且访问资源。因此,我们必须确保要是是唯一不共享的。这里就是这个syncLock对象。把这个对象作为私有的变量是很好的。

    static object syncLock = new object();

    if (_instance == null)
    {
        lock (syncLock)
        {
            if (_instance == null)
            {
                _instance = new LoadBalancer();
            }
        }


    Reader-Writer Lock 这个锁可以被无限制数量的同时读者请求,或者被一个单一的写者请求,如果大多数是读请求很少/时间很短,那么 比Monitor性能更好。读写者在不同的队列里,当写者拥有锁的时候,读者排队等待写者完成,当读者有锁的时候,所有的写者排队。读者和写者交替着完成工作,下面的代码详细解释了。有两个方法。ReadFromCollection 和WriteToCollection 从一个集合里各自的读/写。注意AcquireReaderLock 和 AcquireWriterLock 的使用

    static void Main(string[] args)
            {
                // Thread 1 writing
                new Thread(new ThreadStart(() =>
                    {
                        WriteToCollection(new int[]{1,2,3});

                    })).Start();

                // Thread 2 Reading
                new Thread(new ThreadStart(() =>
                {
                    ReadFromCollection();                
                })).Start();

                // Thread 3 Writing
                new Thread(new ThreadStart(() =>
                {
                    WriteToCollection(new int[] { 4, 5, 6 });

                })).Start();

                // Thread 4 Reading
                new Thread(new ThreadStart(() =>
                {
                    ReadFromCollection();
                })).Start();            

                Console.ReadLine();
            }

            static void ReadFromCollection()
            {
                rwLock.AcquireReaderLock(5000);
                try 
                {
                    Console.WriteLine("Read Lock acquired by thread : {0}  @ {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("hh:mm:ss"));
                    Console.Write("Collection : ");
                    foreach (int item in myCollection)
                    {
                        Console.Write(item + ", ");
                    }
                    Console.Write("n");
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Exception : " + ex.Message);
                }
                finally
                {
                    Console.WriteLine("Read Lock released by thread : {0}  @ {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("hh:mm:ss"));
                    rwLock.ReleaseReaderLock();

                }
            }

            static void WriteToCollection(int[] num)
            {
                rwLock.AcquireWriterLock(5000);
                try
                {
                    Console.WriteLine("Write Lock acquired by thread : {0}  @ {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("hh:mm:ss"));
                    myCollection.AddRange(num);
                    Console.WriteLine("Written to collection ............: {0}", DateTime.Now.ToString("hh:mm:ss"));
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Exception : " + ex.Message);
                }
                finally
                {
                    Console.WriteLine("Write Lock released by thread : {0}  @ {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("hh:mm:ss"));
                    rwLock.ReleaseWriterLock();                
                }
            }   

    Mutex Mutex通常用来在操作系统中共享资源,最好的例子就是检测是否同时有两个同样的进程在运行。

    死锁


    当谈论线程安全的时候,死锁是无法逃避的。
    死锁是两个/多个线程锁住了同样的资源。都在等待对方释放。会导致操作无休止的等待。死锁可以通过认真的编程避免。比如:
    线程A锁住对象A
    线程A锁住对象B
    线程B锁住对象B
    线程B锁住对象A
    线程A等待线程B释放对象B,而线程B等待线程A释放对象A,考虑下面的例子。在一个死锁类里,我们有两个方法OperationA和OperationB嵌套的锁住两个对象,同时运行两个方法。会导致死锁。

    public class DeadLock
    {        
     static object lockA = new object();
     static object lockB = new object();

     public void OperationA()
     {            
      lock (lockA)
      {
       Console.WriteLine("Thread {0} has locked Obect A", Thread.CurrentThread.ManagedThreadId);
       lock (lockB)
       {
        Console.WriteLine("Thread {0} has locked Obect B", Thread.CurrentThread.ManagedThreadId);
       }
       Console.WriteLine("Thread {0} has released Obect B", Thread.CurrentThread.ManagedThreadId);
      }
      Console.WriteLine("Thread {0} has released Obect A", Thread.CurrentThread.ManagedThreadId);
     }

     public void OperationB()
     {            
      lock (lockB)
      {
       Console.WriteLine("Thread {0} has locked Obect B", Thread.CurrentThread.ManagedThreadId);
       lock (lockA)
       {
        Console.WriteLine("Thread {0} has locked Obect A", Thread.CurrentThread.ManagedThreadId);
       }
       Console.WriteLine("Thread {0} has released Obect A", Thread.CurrentThread.ManagedThreadId);
      }
      Console.WriteLine("Thread {0} has released Obect B", Thread.CurrentThread.ManagedThreadId); 

    }  

    DeadLock deadLock = new DeadLock();

     Thread tA = new Thread(new ThreadStart(deadLock.OperationA));
     Thread tB = new Thread(new ThreadStart(deadLock.OperationB));

     Console.WriteLine("Starting Thread A");
     tA.Start();

     Console.WriteLine("Starting Thread B");
     tB.Start();

    工作线程VS I/O线程

    操作系统只有一个线程概念,但是.net 对我们抽象出了一层,我们可以处理两个线程-工作线程和I/O线程,ThreadPool.GetAvailableThreads(out workerThread, out ioThread) 这个方法可以返回给我们可用的每种线程的可用数目。当写代码的时候,程序中繁重的任务应该被分为两部分,计算密集型和I/O密集型。计算密集型是那些CPU运转较多,比如运行查询或是复杂的算法的部分。I/O密集型是那些被用来做一些系统I/O硬件或是网络设备的部分。比如-读写文件,从数据库取数据,查询远程web服务器等。计算密集型应该委托给工作线程,I/O密集型应该被委托给I/O线程。如果我们委托工作线程去做I/O密集型的操作,当设备做这个操作的时候,线程会阻塞,阻塞的线程就是浪费的资源,另一方面,如果我们使用I/O线程做同样的任务,调用线程将会委托任务给设备驱动,自己则回到线程池,当操作完成后,调用线程会从线程池中被通知来处理任务完成。有点事线程保持未阻塞来处理其他的任务,因为当调用线程发起了I/O操作以后,就委托给了操作系统的部分。来处理设备驱动。因此就没有理由阻塞线程了。在.net的类库里。有专门的类型处理I/O线程。比如FileStream类里的BeginRead()和EndRead()方法。所有类似的

    总结

    能力越大,责任越大 –线程池
    1 .没有程序应该在UI线程里做繁重的任务。没有比无响应的UI更难以接受的了。一般情况下,通过使用线程池来管理线程异步执行一些繁重的任务。
    2 .UI不能直接在非UI或是后台线程里更新。程序要需要委托这类工作给UI线程。这可以通过在winform里使用Invoke方法。在WPF里使用Dispatcher方法。或是使用BackGroundWorker自动处理。
    3 .线程是很昂贵的资源应该被认真对待。“越多越热闹”显然是不能接受的。
    4 .在我们的程序里的问题不会通过简单的把工作交给另一个线程就能解决。不会有神奇的事情发生。程序需要被合理的设计。来获得高效率。
    5 .通过Thread类来创建县城的时候要万分小心。调整线程优先级应该慎重。又可以导致其他重要的线程不能执行。
    6 .胡乱设置IsBackground为false可能会引发无法预料的错误。前台线程直到完成才会让程序终止,如果用户想要终止将程序,结果运行在后台的一个任务被设置为了前台线程。导致程序无法终止了。
    7 .多程序中多线程贡献资源的时候,异步线程技术要小心,死锁可以通过认真的编码避免一部分。
    8 .程序员应该确保线程不要太多。闲置线程可能增加开销并且导致内存溢出。
    9 .I/O操作必须委托给I/O线程而不是工作线程。

    展开全文
  • C# 多线程

    2017-08-18 16:25:15
    C# 多线程 学习笔记 一.概念 1.线程被定义为程序的执行路径,每个线程都定义了一个独特的控制流。如果您的程序涉及到复杂的和耗时的操作 那么设置不同线程执行路径是一个好的设计思路,每个线程执行特定的工作。 ...
    C# 多线程 学习笔记
    一.概念
    1.线程被定义为程序的执行路径,每个线程都定义了一个独特的控制流。如果您的程序涉及到复杂的和耗时的操作
    那么设置不同线程执行路径是一个好的设计思路,每个线程执行特定的工作。
    2.线程是轻量级的进程,使用线程节省了CPU的周期浪费,同时提高了应用程序的效率
    3.为了同时执行多个任务,可划分为多个线程
    二.线程的生命周期
    1.未启动状态:当线程实例被创建,但start方法未被调用时
    2.就绪状态:当线程准备好运行并等待CPU周期的情况
    3.不可运行状态:
      已经调用sleep方法
      已经调用wait方法
      通过I/O操作阻塞
    三.主线程
    1.System.Threading.Thread 类用于线程的工作,它允许创建并访问多线程应用程序中的单个线程。进程中第一个被执行的线程称为主线程。
    2.当C#程序开始执行时,主线程自动创建。使用thread类创建的线程被主线程的子线程调用。
    四.Thread 类常用的属性和方法
    ......
    五.创建线程
    展开全文
  • c#多线程

    千次阅读 2018-09-03 10:42:14
    然而,多线程虽然有很多优点,但是也必须认识到多线程可能存在影响系统性能的不利方面,才能正确使用线程。不利方面主要有如下几点: (1)线程也是程序,所以线程需要占用内存,线程越多,占用内存也越多。 ...

    原文地址https://www.cnblogs.com/dotnet261010/p/6159984.html,侵权联删

    一、基本概念

    1、进程

    首先打开任务管理器,查看当前运行的进程:

    从任务管理器里面可以看到当前所有正在运行的进程。那么究竟什么是进程呢?

    进程(Process)是Windows系统中的一个基本概念,它包含着一个运行程序所需要的资源。一个正在运行的应用程序在操作系统中被视为一个进程,进程可以包括一个或多个线程。线程是操作系统分配处理器时间的基本单元,在进程中可以有多个线程同时执行代码。进程之间是相对独立的,一个进程无法访问另一个进程的数据(除非利用分布式计算方式),一个进程运行的失败也不会影响其他进程的运行,Windows系统就是利用进程把工作划分为多个独立的区域的。进程可以理解为一个程序的基本边界。是应用程序的一个运行例程,是应用程序的一次动态执行过程。

    二、线程

    在任务管理器里面查询当前总共运行的线程数:

    线程(Thread)是进程中的基本执行单元,是操作系统分配CPU时间的基本单位,一个进程可以包含若干个线程,在进程入口执行的第一个线程被视为这个进程的主线程。在.NET应用程序中,都是以Main()方法作为入口的,当调用此方法时系统就会自动创建一个主线程。线程主要是由CPU寄存器、调用栈和线程本地存储器(Thread Local Storage,TLS)组成的。CPU寄存器主要记录当前所执行线程的状态,调用栈主要用于维护线程所调用到的内存与数据,TLS主要用于存放线程的状态信息。

    二、多线程

    多线程的优点:可以同时完成多个任务;可以使程序的响应速度更快;可以让占用大量处理时间的任务或当前没有进行处理的任务定期将处理时间让给别的任务;可以随时停止任务;可以设置每个任务的优先级以优化程序性能。

    那么可能有人会问:为什么可以多线程执行呢?总结起来有下面两方面的原因:

    1、CPU运行速度太快,硬件处理速度跟不上,所以操作系统进行分时间片管理。这样,从宏观角度来说是多线程并发的,因为CPU速度太快,察觉不到,看起来是同一时刻执行了不同的操作。但是从微观角度来讲,同一时刻只能有一个线程在处理。

    2、目前电脑都是多核多CPU的,一个CPU在同一时刻只能运行一个线程,但是多个CPU在同一时刻就可以运行多个线程。

    然而,多线程虽然有很多优点,但是也必须认识到多线程可能存在影响系统性能的不利方面,才能正确使用线程。不利方面主要有如下几点:

    (1)线程也是程序,所以线程需要占用内存,线程越多,占用内存也越多。

    (2)多线程需要协调和管理,所以需要占用CPU时间以便跟踪线程。

    (3)线程之间对共享资源的访问会相互影响,必须解决争用共享资源的问题。

    (4)线程太多会导致控制太复杂,最终可能造成很多程序缺陷。

    当启动一个可执行程序时,将创建一个主线程。在默认的情况下,C#程序具有一个线程,此线程执行程序中以Main方法开始和结束的代码,Main()方法直接或间接执行的每一个命令都有默认线程(主线程)执行,当Main()方法返回时此线程也将终止。

    一个进程可以创建一个或多个线程以执行与该进程关联的部分程序代码。在C#中,线程是使用Thread类处理的,该类在System.Threading命名空间中。使用Thread类创建线程时,只需要提供线程入口,线程入口告诉程序让这个线程做什么。通过实例化一个Thread类的对象就可以创建一个线程。创建新的Thread对象时,将创建新的托管线程。Thread类接收一个ThreadStart委托或ParameterizedThreadStart委托的构造函数,该委托包装了调用Start方法时由新线程调用的方法,示例代码如下:

    Thread thread=new Thread(new ThreadStart(method));//创建线程

    thread.Start();                                                           //启动线程

    上面代码实例化了一个Thread对象,并指明将要调用的方法method(),然后启动线程。ThreadStart委托中作为参数的方法不需要参数,并且没有返回值。ParameterizedThreadStart委托一个对象作为参数,利用这个参数可以很方便地向线程传递参数,示例代码如下:

    Thread thread=new Thread(new ParameterizedThreadStart(method));//创建线程

    thread.Start(3);                                                                             //启动线程

    创建多线程的步骤:
    1、编写线程所要执行的方法
    2、实例化Thread类,并传入一个指向线程所要执行方法的委托。(这时线程已经产生,但还没有运行)
    3、调用Thread实例的Start方法,标记该线程可以被CPU执行了,但具体执行时间由CPU决定

    2.1 System.Threading.Thread类

    Thread类是是控制线程的基础类,位于System.Threading命名空间下,具有4个重载的构造函数:

    名称 说明
    Thread(ParameterizedThreadStart)

    初始化 Thread 类的新实例,指定允许对象在线程启动时传递给线程的委托。要执行的方法是有参的。

    Thread(ParameterizedThreadStart, Int32) 初始化 Thread 类的新实例,指定允许对象在线程启动时传递给线程的委托,并指定线程的最大堆栈大小
    Thread(ThreadStart)

    初始化 Thread 类的新实例。要执行的方法是无参的。

    Thread(ThreadStart, Int32)

    初始化 Thread 类的新实例,指定线程的最大堆栈大小。

    ThreadStart是一个无参的、返回值为void的委托。委托定义如下:

    public delegate void ThreadStart()

    通过ThreadStart委托创建并运行一个线程:

    复制代码

     1  class Program
     2     {
     3         static void Main(string[] args)
     4         {
     5             //创建无参的线程
     6             Thread thread1 = new Thread(new ThreadStart(Thread1));
     7             //调用Start方法执行线程
     8             thread1.Start();
     9 
    10             Console.ReadKey();
    11         }
    12 
    13         /// <summary>
    14         /// 创建无参的方法
    15         /// </summary>
    16         static void Thread1()
    17         {
    18             Console.WriteLine("这是无参的方法");
    19         }
    20     }

    复制代码

    运行结果

    除了可以运行静态的方法,还可以运行实例方法

    复制代码

     1  class Program
     2     {
     3         static void Main(string[] args)
     4         {
     5             //创建ThreadTest类的一个实例
     6             ThreadTest test=new ThreadTest();
     7             //调用test实例的MyThread方法
     8             Thread thread = new Thread(new ThreadStart(test.MyThread));
     9             //启动线程
    10             thread.Start();
    11             Console.ReadKey();
    12         }
    13     }
    14 
    15     class ThreadTest
    16     {
    17         public void MyThread()
    18         {
    19             Console.WriteLine("这是一个实例方法");
    20         }
    21     }

    复制代码

    运行结果:

    如果为了简单,也可以通过匿名委托或Lambda表达式来为Thread的构造方法赋值

    复制代码

     1  static void Main(string[] args)
     2  {
     3        //通过匿名委托创建
     4        Thread thread1 = new Thread(delegate() { Console.WriteLine("我是通过匿名委托创建的线程"); });
     5        thread1.Start();
     6        //通过Lambda表达式创建
     7        Thread thread2 = new Thread(() => Console.WriteLine("我是通过Lambda表达式创建的委托"));
     8        thread2.Start();
     9        Console.ReadKey();
    10  }

    复制代码

     

     运行结果:

    ParameterizedThreadStart是一个有参的、返回值为void的委托,定义如下:

    public delegate void ParameterizedThreadStart(Object obj)

    复制代码

     1  class Program
     2     {
     3         static void Main(string[] args)
     4         {
     5             //通过ParameterizedThreadStart创建线程
     6             Thread thread = new Thread(new ParameterizedThreadStart(Thread1));
     7             //给方法传值
     8             thread.Start("这是一个有参数的委托");
     9             Console.ReadKey();
    10         }
    11 
    12         /// <summary>
    13         /// 创建有参的方法
    14         /// 注意:方法里面的参数类型必须是Object类型
    15         /// </summary>
    16         /// <param name="obj"></param>
    17         static void Thread1(object obj)
    18         {
    19             Console.WriteLine(obj);
    20         }
    21     }

    复制代码

    注意:ParameterizedThreadStart委托的参数类型必须是Object的。如果使用的是不带参数的委托,不能使用带参数的Start方法运行线程,否则系统会抛出异常。但使用带参数的委托,可以使用thread.Start()来运行线程,这时所传递的参数值为null。

    2.2 线程的常用属性

    属性名称 说明
    CurrentContext 获取线程正在其中执行的当前上下文。
    CurrentThread 获取当前正在运行的线程。
    ExecutionContext 获取一个 ExecutionContext 对象,该对象包含有关当前线程的各种上下文的信息。
    IsAlive 获取一个值,该值指示当前线程的执行状态。
    IsBackground 获取或设置一个值,该值指示某个线程是否为后台线程。
    IsThreadPoolThread 获取一个值,该值指示线程是否属于托管线程池。
    ManagedThreadId 获取当前托管线程的唯一标识符。
    Name 获取或设置线程的名称。
    Priority 获取或设置一个值,该值指示线程的调度优先级。
    ThreadState 获取一个值,该值包含当前线程的状态。

    2.2.1 线程的标识符

    ManagedThreadId是确认线程的唯一标识符,程序在大部分情况下都是通过Thread.ManagedThreadId来辨别线程的。而Name是一个可变值,在默认时候,Name为一个空值 Null,开发人员可以通过程序设置线程的名称,但这只是一个辅助功能。

     

    2.2.2 线程的优先级别

    当线程之间争夺CPU时间时,CPU按照线程的优先级给予服务。高优先级的线程可以完全阻止低优先级的线程执行。.NET为线程设置了Priority属性来定义线程执行的优先级别,里面包含5个选项,其中Normal是默认值。除非系统有特殊要求,否则不应该随便设置线程的优先级别。

    成员名称 说明
    Lowest 可以将 Thread 安排在具有任何其他优先级的线程之后。
    BelowNormal 可以将 Thread 安排在具有 Normal 优先级的线程之后,在具有 Lowest 优先级的线程之前。
    Normal 默认选择。可以将 Thread 安排在具有 AboveNormal 优先级的线程之后,在具有BelowNormal 优先级的线程之前。
    AboveNormal 可以将 Thread 安排在具有 Highest 优先级的线程之后,在具有 Normal 优先级的线程之前。
    Highest 可以将 Thread 安排在具有任何其他优先级的线程之前。

     

    2.2.3 线程的状态

    通过ThreadState可以检测线程是处于Unstarted、Sleeping、Running 等等状态,它比 IsAlive 属性能提供更多的特定信息。

    前面说过,一个应用程序域中可能包括多个上下文,而通过CurrentContext可以获取线程当前的上下文。

    CurrentThread是最常用的一个属性,它是用于获取当前运行的线程。

     

    2.2.4 System.Threading.Thread的方法

    Thread 中包括了多个方法来控制线程的创建、挂起、停止、销毁,以后来的例子中会经常使用。

    方法名称 说明
    Abort()     终止本线程。
    GetDomain() 返回当前线程正在其中运行的当前域。
    GetDomainId() 返回当前线程正在其中运行的当前域Id。
    Interrupt() 中断处于 WaitSleepJoin 线程状态的线程。
    Join() 已重载。 阻塞调用线程,直到某个线程终止时为止。
    Resume() 继续运行已挂起的线程。
    Start()   执行本线程。
    Suspend() 挂起当前线程,如果当前线程已属于挂起状态则此不起作用
    Sleep()   把正在运行的线程挂起一段时间。

    线程示例

    复制代码

     1     static void Main(string[] args)
     2         {
     3             //获取正在运行的线程
     4             Thread thread = Thread.CurrentThread;
     5             //设置线程的名字
     6             thread.Name = "主线程";
     7             //获取当前线程的唯一标识符
     8             int id = thread.ManagedThreadId;
     9             //获取当前线程的状态
    10             ThreadState state= thread.ThreadState;
    11             //获取当前线程的优先级
    12             ThreadPriority priority= thread.Priority;
    13             string strMsg = string.Format("Thread ID:{0}\n" + "Thread Name:{1}\n" +
    14                 "Thread State:{2}\n" + "Thread Priority:{3}\n", id, thread.Name,
    15                 state, priority);
    16 
    17             Console.WriteLine(strMsg);
    18                       
    19             Console.ReadKey();
    20         }

    复制代码

    运行结果:

    2.3 前台线程和后台线程

    前台线程:只有所有的前台线程都结束,应用程序才能结束。默认情况下创建的线程
                  都是前台线程
    后台线程:只要所有的前台线程结束,后台线程自动结束。通过Thread.IsBackground设置后台线程。必须在调用Start方法之前设置线程的类型,否则一旦线程运行,将无法改变其类型。

    通过BeginXXX方法运行的线程都是后台线程。

    复制代码

     1 class Program
     2     {
     3         static void Main(string[] args)
     4         {                   
     5             //演示前台、后台线程
     6             BackGroundTest background = new BackGroundTest(10);
     7             //创建前台线程
     8             Thread fThread = new Thread(new ThreadStart(background.RunLoop));
     9             //给线程命名
    10             fThread.Name = "前台线程";
    11             
    12 
    13             BackGroundTest background1 = new BackGroundTest(20);
    14             //创建后台线程
    15             Thread bThread = new Thread(new ThreadStart(background1.RunLoop));
    16             bThread.Name = "后台线程";
    17             //设置为后台线程
    18             bThread.IsBackground = true;
    19 
    20             //启动线程
    21             fThread.Start();
    22             bThread.Start();
    23         }
    24     }
    25 
    26     class BackGroundTest
    27     {
    28         private int Count;
    29         public BackGroundTest(int count)
    30         {
    31             this.Count = count;
    32         }
    33         public void RunLoop()
    34         {
    35             //获取当前线程的名称
    36             string threadName = Thread.CurrentThread.Name;
    37             for (int i = 0; i < Count; i++)
    38             {
    39                 Console.WriteLine("{0}计数:{1}",threadName,i.ToString());
    40                 //线程休眠500毫秒
    41                 Thread.Sleep(1000);
    42             }
    43             Console.WriteLine("{0}完成计数",threadName);
    44             
    45         }
    46     }

    复制代码

    运行结果:前台线程执行完,后台线程未执行完,程序自动结束。

    把bThread.IsBackground = true注释掉,运行结果:主线程执行完毕后(Main函数),程序并未结束,而是要等所有的前台线程结束以后才会结束。

    后台线程一般用于处理不重要的事情,应用程序结束时,后台线程是否执行完成对整个应用程序没有影响。如果要执行的事情很重要,需要将线程设置为前台线程。

    2.4 线程同步

    所谓同步:是指在某一时刻只有一个线程可以访问变量。
    如果不能确保对变量的访问是同步的,就会产生错误。
    c#为同步访问变量提供了一个非常简单的方式,即使用c#语言的关键字Lock,它可以把一段代码定义为互斥段,互斥段在一个时刻内只允许一个线程进入执行,而其他线程必须等待。在c#中,关键字Lock定义如下:
    Lock(expression)
    {
       statement_block
    }

    expression代表你希望跟踪的对象:
               如果你想保护一个类的实例,一般地,你可以使用this;
               如果你想保护一个静态变量(如互斥代码段在一个静态方法内部),一般使用类名就可以了
    而statement_block就算互斥段的代码,这段代码在一个时刻内只可能被一个线程执行。

    以书店卖书为例

    复制代码

     1 class Program
     2     {
     3         static void Main(string[] args)
     4         {                   
     5             BookShop book = new BookShop();
     6             //创建两个线程同时访问Sale方法
     7             Thread t1 = new Thread(new ThreadStart(book.Sale));
     8             Thread t2 = new Thread(new ThreadStart(book.Sale));
     9             //启动线程
    10             t1.Start();
    11             t2.Start();
    12             Console.ReadKey();
    13         }
    14     }
    15 
    16    
    17 
    18     class BookShop
    19     {
    20         //剩余图书数量
    21         public int num = 1;
    22         public void Sale()
    23         {
    24             int tmp = num;
    25             if (tmp > 0)//判断是否有书,如果有就可以卖
    26             {
    27                 Thread.Sleep(1000);
    28                 num -= 1;
    29                 Console.WriteLine("售出一本图书,还剩余{0}本", num);
    30             }
    31             else
    32             {
    33                 Console.WriteLine("没有了");
    34             }
    35         }
    36     }

    复制代码

    运行结果:

    从运行结果可以看出,两个线程同步访问共享资源,没有考虑同步的问题,结果不正确。

    考虑线程同步,改进后的代码:

    复制代码

     1 class Program
     2     {
     3         static void Main(string[] args)
     4         {                   
     5             BookShop book = new BookShop();
     6             //创建两个线程同时访问Sale方法
     7             Thread t1 = new Thread(new ThreadStart(book.Sale));
     8             Thread t2 = new Thread(new ThreadStart(book.Sale));
     9             //启动线程
    10             t1.Start();
    11             t2.Start();
    12             Console.ReadKey();
    13         }
    14     }
    15 
    16    
    17 
    18     class BookShop
    19     {
    20         //剩余图书数量
    21         public int num = 1;
    22         public void Sale()
    23         {
    24             //使用lock关键字解决线程同步问题
    25             lock (this)
    26             {
    27                 int tmp = num;
    28                 if (tmp > 0)//判断是否有书,如果有就可以卖
    29                 {
    30                     Thread.Sleep(1000);
    31                     num -= 1;
    32                     Console.WriteLine("售出一本图书,还剩余{0}本", num);
    33                 }
    34                 else
    35                 {
    36                     Console.WriteLine("没有了");
    37                 }
    38             }
    39         }
    40     }

    复制代码

    运行结果:

    2.5 跨线程访问

    点击“测试”,创建一个线程,从0循环到10000给文本框赋值,代码如下:

    复制代码

     1  private void btn_Test_Click(object sender, EventArgs e)
     2         {
     3             //创建一个线程去执行这个方法:创建的线程默认是前台线程
     4             Thread thread = new Thread(new ThreadStart(Test));
     5             //Start方法标记这个线程就绪了,可以随时被执行,具体什么时候执行这个线程,由CPU决定
     6             //将线程设置为后台线程
     7             thread.IsBackground = true;
     8             thread.Start();
     9         }
    10 
    11         private void Test()
    12         {
    13             for (int i = 0; i < 10000; i++)
    14             {               
    15                 this.textBox1.Text = i.ToString();
    16             }
    17         }

    复制代码

    运行结果:

    产生错误的原因:textBox1是由主线程创建的,thread线程是另外创建的一个线程,在.NET上执行的是托管代码,C#强制要求这些代码必须是线程安全的,即不允许跨线程访问Windows窗体的控件。

    解决方案:

    1、在窗体的加载事件中,将C#内置控件(Control)类的CheckForIllegalCrossThreadCalls属性设置为false,屏蔽掉C#编译器对跨线程调用的检查。

     private void Form1_Load(object sender, EventArgs e)
     {
            //取消跨线程的访问
            Control.CheckForIllegalCrossThreadCalls = false;
     }

    使用上述的方法虽然可以保证程序正常运行并实现应用的功能,但是在实际的软件开发中,做如此设置是不安全的(不符合.NET的安全规范),在产品软件的开发中,此类情况是不允许的。如果要在遵守.NET安全标准的前提下,实现从一个线程成功地访问另一个线程创建的空间,要使用C#的方法回调机制。

    2、使用回调函数

    回调实现的一般过程:

     C#的方法回调机制,也是建立在委托基础上的,下面给出它的典型实现过程。

    (1)、定义、声明回调。

    1 //定义回调
    2 private delegate void DoSomeCallBack(Type para);
    3 //声明回调
    4 DoSomeCallBack doSomaCallBack;

    可以看出,这里定义声明的“回调”(doSomaCallBack)其实就是一个委托。

    (2)、初始化回调方法。

    doSomeCallBack=new DoSomeCallBack(DoSomeMethod);

    所谓“初始化回调方法”实际上就是实例化刚刚定义了的委托,这里作为参数的DoSomeMethod称为“回调方法”,它封装了对另一个线程中目标对象(窗体控件或其他类)的操作代码。

    (3)、触发对象动作

    Opt  obj.Invoke(doSomeCallBack,arg);

    其中Opt obj为目标操作对象,在此假设它是某控件,故调用其Invoke方法。Invoke方法签名为:

    object  Control.Invoke(Delegate  method,params  object[] args);

    它的第一个参数为委托类型,可见“触发对象动作”的本质,就是把委托doSomeCallBack作为参数传递给控件的Invoke方法,这与委托的使用方式是一模一样的。

    最终作用于对象Opt obj的代码是置于回调方法体DoSomeMethod()中的,如下所示:

    private void DoSomeMethod(type para)

    {

         //方法体

        Opt obj.someMethod(para);

    }

    如果不用回调,而是直接在程序中使用“Opt obj.someMethod(para);”,则当对象Opt obj不在本线程(跨线程访问)时就会发生上面所示的错误。

    从以上回调实现的一般过程可知:C#的回调机制,实质上是委托的一种应用。在C#网络编程中,回调的应用是非常普遍的,有了方法回调,就可以在.NET上写出线程安全的代码了。

    使用方法回调,实现给文本框赋值:

    复制代码

     1 namespace MultiThreadDemo
     2 {
     3     public partial class Form1 : Form
     4     {
     5         public Form1()
     6         {
     7             InitializeComponent();
     8         }
     9 
    10         //定义回调
    11         private delegate void setTextValueCallBack(int value);
    12         //声明回调
    13         private setTextValueCallBack setCallBack;
    14 
    15         private void btn_Test_Click(object sender, EventArgs e)
    16         {
    17             //实例化回调
    18             setCallBack = new setTextValueCallBack(SetValue);
    19             //创建一个线程去执行这个方法:创建的线程默认是前台线程
    20             Thread thread = new Thread(new ThreadStart(Test));
    21             //Start方法标记这个线程就绪了,可以随时被执行,具体什么时候执行这个线程,由CPU决定
    22             //将线程设置为后台线程
    23             thread.IsBackground = true;
    24             thread.Start();
    25         }
    26 
    27         private void Test()
    28         {
    29             for (int i = 0; i < 10000; i++)
    30             {               
    31                 //使用回调
    32                 textBox1.Invoke(setCallBack, i);
    33             }
    34         }
    35 
    36         /// <summary>
    37         /// 定义回调使用的方法
    38         /// </summary>
    39         /// <param name="value"></param>
    40         private void SetValue(int value)
    41         {
    42             this.textBox1.Text = value.ToString();
    43         }
    44     }
    45 }

    复制代码

     2.6 终止线程

    若想终止正在运行的线程,可以使用Abort()方法。

    三、同步和异步

    同步和异步是对方法执行顺序的描述。

    同步:等待上一行完成计算之后,才会进入下一行。

    例如:请同事吃饭,同事说很忙,然后就等着同事忙完,然后一起去吃饭。

    异步:不会等待方法的完成,会直接进入下一行,是非阻塞的。

    例如:请同事吃饭,同事说很忙,那同事先忙,自己去吃饭,同事忙完了他自己去吃饭。

    下面通过一个例子讲解同步和异步的区别

    1、新建一个winform程序,上面有两个按钮,一个同步方法、一个异步方法,在属性里面把输出类型改成控制台应用程序,这样可以看到输出结果,代码如下:

    复制代码

     1 using System;
     2 using System.Collections.Generic;
     3 using System.ComponentModel;
     4 using System.Data;
     5 using System.Drawing;
     6 using System.Linq;
     7 using System.Text;
     8 using System.Threading;
     9 using System.Threading.Tasks;
    10 using System.Windows.Forms;
    11 
    12 namespace MyAsyncThreadDemo
    13 {
    14     public partial class Form1 : Form
    15     {
    16         public Form1()
    17         {
    18             InitializeComponent();
    19         }
    20 
    21         /// <summary>
    22         /// 异步方法
    23         /// </summary>
    24         /// <param name="sender"></param>
    25         /// <param name="e"></param>
    26         private void btnAsync_Click(object sender, EventArgs e)
    27         {
    28             Console.WriteLine($"***************btnAsync_Click Start {Thread.CurrentThread.ManagedThreadId}");
    29             Action<string> action = this.DoSomethingLong;
    30             // 调用委托(同步调用)
    31             action.Invoke("btnAsync_Click_1");
    32             // 异步调用委托
    33             action.BeginInvoke("btnAsync_Click_2",null,null);
    34             Console.WriteLine($"***************btnAsync_Click End    {Thread.CurrentThread.ManagedThreadId}");
    35         }
    36 
    37         /// <summary>
    38         /// 同步方法
    39         /// </summary>
    40         /// <param name="sender"></param>
    41         /// <param name="e"></param>
    42         private void btnSync_Click(object sender, EventArgs e)
    43         {
    44             Console.WriteLine($"****************btnSync_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
    45             int j = 3;
    46             int k = 5;
    47             int m = j + k;
    48             for (int i = 0; i < 5; i++)
    49             {
    50                 string name = string.Format($"btnSync_Click_{i}");
    51                 this.DoSomethingLong(name);
    52             }
    53         }
    54 
    55 
    56         private void DoSomethingLong(string name)
    57         {
    58             Console.WriteLine($"****************DoSomethingLong {name} Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
    59             long lResult = 0;
    60             for (int i = 0; i < 1000000000; i++)
    61             {
    62                 lResult += i;
    63             }
    64             Console.WriteLine($"****************DoSomethingLong {name}   End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {lResult}***************");
    65         }
    66     }
    67 }

    复制代码

     2、启动程序,点击同步,结果如下:

    从上面的截图中能够很清晰的看出:同步方法是等待上一行代码执行完毕之后才会执行下一行代码。

    点击异步,结果如下:

    从上面的截图中看出:当执行到action.BeginInvoke("btnAsync_Click_2",null,null);这句代码的时候,程序并没有等待这段代码执行完就执行了下面的End,没有阻塞程序的执行。

    在刚才的测试中,如果点击同步,这时winform界面不能拖到,界面卡住了,是因为主线程(即UI线程)在忙于计算。

    点击异步的时候,界面不会卡住,这是因为主线程已经结束,计算任务交给子线程去做。

    在仔细检查上面两个截图,可以看出异步的执行速度比同步执行速度要快。同步方法执行完将近16秒,异步方法执行完将近6秒。

    在看下面的一个例子,修改异步的方法,也和同步方法一样执行循环,修改后的代码如下:

    复制代码

     1 private void btnAsync_Click(object sender, EventArgs e)
     2 {
     3       Console.WriteLine($"***************btnAsync_Click Start {Thread.CurrentThread.ManagedThreadId}");
     4       //Action<string> action = this.DoSomethingLong;
     5        调用委托(同步调用)
     6       //action.Invoke("btnAsync_Click_1");
     7        异步调用委托
     8       //action.BeginInvoke("btnAsync_Click_2",null,null);
     9       Action<string> action = this.DoSomethingLong;
    10       for (int i = 0; i < 5; i++)
    11       {
    12            //Thread.Sleep(5);
    13            string name = string.Format($"btnAsync_Click_{i}");
    14            action.BeginInvoke(name, null, null);
    15       }
    16       Console.WriteLine($"***************btnAsync_Click End    {Thread.CurrentThread.ManagedThreadId}");
    17 }

    复制代码

     结果如下:

    从截图中能够看出:同步方法执行是有序的,异步方法执行是无序的。异步方法无序包括启动无序和结束无序。启动无序是因为同一时刻向操作系统申请线程,操作系统收到申请以后,返回执行的顺序是无序的,所以启动是无序的。结束无序是因为虽然线程执行的是同样的操作,但是每个线程的耗时是不同的,所以结束的时候不一定是先启动的线程就先结束。从上面同步方法中可以清晰的看出:btnSync_Click_0执行时间耗时不到3秒,而btnSync_Click_1执行时间耗时超过了3秒。可以想象体育比赛中的跑步,每位运动员听到发令枪起跑的顺序不同,每位运动员花费的时间不同,最终到达终点的顺序也不同。

    总结一下同步方法和异步方法的区别:

    1、同步方法由于主线程忙于计算,所以会卡住界面。

          异步方法由于主线程执行完了,其他计算任务交给子线程去执行,所以不会卡住界面,用户体验性好。

    2、同步方法由于只有一个线程在计算,所以执行速度慢。

          异步方法由多个线程并发运算,所以执行速度快,但并不是线性增长的(资源可能不够)。多线程也不是越多越好,只有多个独立的任务同时运行,才能加快速度。

    3、同步方法是有序的。

          异步多线程是无序的:启动无序,执行时间不确定,所以结束也是无序的。一定不要通过等待几毫秒的形式来控制线程启动/执行时间/结束。

    四、回调

    先来看看异步多线程无序的例子:

    在界面上新增一个按钮,实现代码如下:

    复制代码

    1 private void btnAsyncAdvanced_Click(object sender, EventArgs e)
    2 {
    3       Console.WriteLine($"****************btnAsyncAdvanced_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
    4       Action<string> action = this.DoSomethingLong;
    5       action.BeginInvoke("btnAsyncAdvanced_Click", null, null);
    6       // 需求:异步多线程执行完之后再打印出下面这句
    7       Console.WriteLine($"到这里计算已经完成了。{Thread.CurrentThread.ManagedThreadId.ToString("00")}。");
    8       Console.WriteLine($"****************btnAsyncAdvanced_Click End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
    9 }

    复制代码

     

    运行结果:

    从上面的截图中看出,最终的效果并不是我们想要的效果,而且打印输出的还是主线程。

    既然异步多线程是无序的,那我们有没有什么办法可以解决无序的问题呢?办法当然是有的,那就是使用回调,.NET框架已经帮我们实现了回调:

    BeginInvoke的第二个参数就是一个回调,那么AsyncCallback究竟是什么呢?F12查看AsyncCallback的定义:

    发现AsyncCallback就是一个委托,参数类型是IAsyncResult,明白了AsyncCallback是什么以后,将上面的代码进行如下的改造:

    复制代码

     1 private void btnAsyncAdvanced_Click(object sender, EventArgs e)
     2 {       
     3     Console.WriteLine($"****************btnAsyncAdvanced_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
     4     Action<string> action = this.DoSomethingLong;
     5     // 定义一个回调
     6     AsyncCallback callback = p => 
     7     {
     8        Console.WriteLine($"到这里计算已经完成了。{Thread.CurrentThread.ManagedThreadId.ToString("00")}。");
     9     };
    10     // 回调作为参数
    11     action.BeginInvoke("btnAsyncAdvanced_Click", callback, null);          
    12     Console.WriteLine($"****************btnAsyncAdvanced_Click End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
    13  }

    复制代码

     

    运行结果:

    上面的截图中可以看出,这就是我们想要的效果,而且打印是子线程输出的,但是程序究竟是怎么实现的呢?我们可以进行如下的猜想:

    程序执行到BeginInvoke的时候,会申请一个基于线程池的线程,这个线程会完成委托的执行(在这里就是执行DoSomethingLong()方法),在委托执行完以后,这个线程又会去执行callback回调的委托,执行callback委托需要一个IAsyncResult类型的参数,这个IAsyncResult类型的参数是如何来的呢?鼠标右键放到BeginInvoke上面,查看返回值:

    发现BeginInvoke的返回值就是IAsyncResult类型的。那么这个返回值是不是就是callback委托的参数呢?将代码进行如下的修改:

    复制代码

     1 private void btnAsyncAdvanced_Click(object sender, EventArgs e)
     2 {
     3             // 需求:异步多线程执行完之后再打印出下面这句
     4             Console.WriteLine($"****************btnAsyncAdvanced_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
     5             Action<string> action = this.DoSomethingLong;
     6             // 无序的
     7             //action.BeginInvoke("btnAsyncAdvanced_Click", null, null);
     8 
     9             IAsyncResult asyncResult = null;
    10             // 定义一个回调
    11             AsyncCallback callback = p =>
    12             {
    13                 // 比较两个变量是否是同一个
    14                 Console.WriteLine(object.ReferenceEquals(p,asyncResult));
    15                 Console.WriteLine($"到这里计算已经完成了。{Thread.CurrentThread.ManagedThreadId.ToString("00")}。");
    16             };
    17             // 回调作为参数
    18             asyncResult= action.BeginInvoke("btnAsyncAdvanced_Click", callback, null);           
    19             Console.WriteLine($"****************btnAsyncAdvanced_Click End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
    20 }

    复制代码

     结果:

    这里可以看出BeginInvoke的返回值就是callback委托的参数。

    现在我们可以使用回调解决异步多线程无序的问题了。

    2、获取委托异步调用的返回值

    使用EndInvoke可以获取委托异步调用的返回值,请看下面的例子:

    复制代码

     1 private void btnAsyncReturnVlaue_Click(object sender, EventArgs e)
     2 {
     3        // 定义一个无参数、int类型返回值的委托
     4        Func<int> func = () =>
     5        {
     6              Thread.Sleep(2000);
     7              return DateTime.Now.Day;
     8        };
     9        // 输出委托同步调用的返回值
    10        Console.WriteLine($"func.Invoke()={func.Invoke()}");
    11        // 委托的异步调用
    12        IAsyncResult asyncResult = func.BeginInvoke(p => 
    13        {
    14             Console.WriteLine(p.AsyncState);
    15        },"异步调用返回值");
    16        // 输出委托异步调用的返回值
    17        Console.WriteLine($"func.EndInvoke(asyncResult)={func.EndInvoke(asyncResult)}");
    18 }

    复制代码

     结果:

     

    展开全文
  • C#C#开发C#语言C#多线程解决程序卡顿问题 描述:在 C# 中,System.Threading.Thread 类用于线程的工作。它允许创建并访问多线程应用程序中的单个线程。进程中第一个被执行的线程称为主线程。案例:static void Main...

    C#

    C#开发

    C#语言

    C#多线程解决程序卡顿问题

    描述:

    在 C# 中,System.Threading.Thread 类用于线程的工作。它允许创建并访问多线程应用程序中的单个线程。进程中第一个被执行的线程称为主线程。

    案例:

    static void Main(string[] args)

    {

    int num = 100;

    for (int i = 0; i < num; i++)

    {

    //无参的多线程

    noParmaThread();

    }

    }

    private static void StartThread()

    {

    Console.WriteLine("------开始了新线程------");

    Thread.Sleep(2000);//wait

    Console.WriteLine("------线程结束------");

    }

    ///

    ///不需要传递参数

    ///

    private static void noParmaThread()

    {

    ThreadStart threadStart = new ThreadStart(StartThread);

    var thread = new Thread(threadStart);

    thread.Start();//开始线程

    }

    内容来源于网络,如有侵权请联系客服删除

    展开全文
  • C#多线程Demo

    2012-04-10 10:50:40
    C#多线程Demo程序 C#多线程Demo C#多线程Demo C#多线程Demo

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 14,970
精华内容 5,988
关键字:

c#多线程

c# 订阅