线程 方面笔记01

参考:https://blog.csdn.net/qq_35040828/article/details/73123996

1. 基础语法 

  

    static void Main()
    {
        Thread t = new Thread(WriteY);
        t.Start();
    }
    static void WriteY()
    {
        while (true)
            Console.Write("y");
    }

2.线程安全

1)排他锁

解决方法 提供一个排他锁

    static bool done;
    static object locker;
    static void Main()
    {
        new Thread(Go).Start();
        Go();
    }
    static void Go()
    {
        lock (locker)
        {
            if (!done)
            {
                Console.WriteLine("Done");
                done = true;
            }
        }
    }

  停止线程的方法  :  锁  、 或sleep   、或 用join方法等待另一个线程结束 

2)暂停或者sleep

写在调用方法里 ?      

Thread.Sleep(TimeSpan.FromSeconds(30));

3) joint方法 等待另一个线程结束:

Thread t = new Thread(Go);         
 //Assume Go is some static method
t.Start();
t.Join();                            
//Wait (block) until thread t ends

 3.创建和开始使用多线程

线程用Thread类来创建, 通过ThreadStart委托来指明方法从哪里开始运行,下面是ThreadStart委托如何定义的:

public delegate void ThreadStart();

1)创建方法

下面是一个例子,使用了C#的语法创建TheadStart委托:

Thread t = new Thread (new ThreadStart(Go));
t.Start();  

一个线程可以通过C#堆委托简短的语法更便利地创建出来:

Thread t = new Thread(Go);    
//No need to explicitly use ThreadStart t.Start();

 还有一种方法是使用匿名的方法来启动

 Thread t = new Thread(delegate(){ Console.WriteLine ("Hello!"); });
 t.Start();

 线程有一个IsAlive属性,在调用Start()之后直到线程结束之前一直为true。一个线程一旦结束便不能重新开始了。

2)将数据传入ThreadStart中

threadStart  不接受参数

ParameterizedThreadStart 可以接收一个单独的object类型参数

public delegate void ParameterizedThreadStart (object obj);

例如:

static void Main()
{
     Thread t = new Thread(go);
     t.start(true);
     //go(true)
     go(false);
}
static void go(object upperCase)
{
    bool upper = (bool)upperCase;
    Console.writeLine(upper?“Hello”:"hello");
}

  在整个例子中,编译器自动推断出ParameterizedThreadStart委托,因为Go方法接收一个单独的object参数,就像这样写:

Threadt = new Thread(new ParameterizedThreadStart (Go));
t.Start(true);

ParameterizedThreadStart的特性是在使用之前,我们必须对我们想要的类型(例如bool)进行装箱 操作,并且 只能接收一个参数,一个

static void Main()
 {
    Thread t = new Thread(delegate(){ WriteText ("Hello");});
    t.Start();
}
static void WriteText(string text)
 { Console.WriteLine (text); }

优点是目标方法可以接收任意数量的参数,并且没有装箱操作。

不过如果将一个外部变量放入匿名方法中

 例如:

static void Main()
 {
  string text= "Before";
  Thread t = new Thread(delegate() { WriteText (text); });
  text= "After";
  t.Start();
}
static void WriteText(string text)
 { Console.WriteLine (text); }

当外部变量被后来的部分修改了值的时候,可能会透过外部变量进行无意的互动,外部变量最好被处理成只读的,除非添加锁。

另一个方法是将对象实例的方法而不是静态方法传入到线程中,对象实例的属性告诉线程要做什么:

class ThreadTest
 {
  bool upper;
  
  static void Main()
 {
    ThreadTest instance1 = new ThreadTest();
    instance1.upper= true;
    Thread t = new Thread(instance1.Go);
    t.Start();
    ThreadTest instance2 = new ThreadTest();
    instance2.Go();       
//主线程——运行 upper=false
  }
  
  void Go()
 { Console.WriteLine (upper ? "HELLO!" :"hello!");
 }

 4.命名线程

线程可以通过name属性进行 命名,线程的名字可以在任意时候进行设置,但是只能设置一次 ,崇明会引发异常。

程序的主线程也可以被命名,例:

        static void Main()
        {
            Thread.CurrentThread.Name = "main";
            Thread work = new Thread(go);
            work.Name = "worker";
            work.Start();
            go();
            Console.ReadLine();


        }
        static void go ()
        {
            Console.WriteLine("Hello from " + Thread.CurrentThread.Name);
        }

5.前台和后台线程

线程默认为前台线程。

后台线程,当所有的前台线程结束之后,不维持程序的存活

IsBackground 属性控制它的前后台状态,例:

        static void Main(string []args)
        {
            Thread worker = new Thread(delegate () { Console.ReadLine(); });
            if (args.Length > 0) worker.IsBackground = true;
            worker.Start();
        }

   如果程序被调用的时候没有任何参数,工作线程为前台线程,并且将等待ReadLine语句来等待用户的触发回车,这期间,主线程退出,但是程序保持运行,因为一个前台线程仍然活着。

   另一方面如果有参数传入Main(),工作线程被赋值为后台线程,当主线程结束程序立刻退出,终止了ReadLine。

后台线程终止 的这种方式 ,使任何最后的操作都 被规避了 ,这种方式是不太合适的,应该等后台工作线程完成后再结束程序 ,可以使用timeout(或大部分时候使用Threa.join)。

如何因为某种原因,某个线程无法完成,可以用试图终于它的方式,如果失败了再抛弃线程,允许它与进行一起消亡?

拥有一个后台工作线程是有益的,最直接的理由是:当结束程序的时候它总是可能有最后 的发言权,交互不会消亡的前台线程,保证程序的正常退出。抛弃一个前台工作线程是尤为危险的,尤其是对windowform程序 ,因为我程序直到主线程结束时才退出,但是进程依然运行着。在Windows任务管理器它将从应用程序栏消失不见,但却可以在进程栏找到它。除非用户找到并结束它,它将继续消耗资源,并可能阻止一个新的实例的运行从开始或影响它的特性。

对于程序失败退出的普遍原因就是存在“被忘记”的前台线程。

6.线程的优先级

线程的priority 属性确定了线程 相对于其他同一进程的活动的线程拥有多少执行时间,以下是级别:

enum ThreadPriority{ Lowest,BelowNormal,normal,AboveNormal,Highest }

只有有多个线程同时为活动时,优先级才有作用 。

设置 一个线程的优先级为高一些 ,并不意味着它能执行实时的工作,因为它受限于程序 的进程的级别。要执行实时的工作,必须 提升在System.Diagnostics 命名空间下Process的级别,像下面这样:

Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;

ProcessPriorityClass.High 其实是一个短暂缺口的过程中的最高优先级别:Realtime。设置进程级别到Realtime通知操作系统:你不想让你的进程被抢占了。如果你的程序进入一个偶然的死循环,可以预期,操作系统被锁住了,除了关机没有什么可以拯救你了!基于此,High大体上被认为最高的有用进程级别。

   如果一个实时的程序有一个用户界面,提升进程的级别是不太好的,因为当用户界面UI过于复杂的时候,界面的更新耗费过多的CPU时间,拖慢了整台电脑。(虽然在写这篇文章的时候,在互联网电话程序Skype侥幸地这么做, 也许是因为它的界面相当简单吧。) 降低主线程的级别、提升进程的级别、确保实时线程不进行界面刷新,但这样并不能避免电脑越来越慢,因为操作系统仍会拨出过多的CPU给整个进程。最理想的方案是使实时工作和用户界面在不同的进程(拥有不同的优先级)运行,通过Remoting或共享内存方式进行通信,共享内存需要Win32 API中的 P/Invoking。(可以搜索看看CreateFileMapping  MapViewOfFile)

7.异常处理

任何线程创建范围内 try/catch/finally块,当线程开始执行便不再与其有任何关系。

        static void Main()
        {
            try
            {
                new Thread(go).Start();
            }
            catch (Exception ex)
            {
                //不会在这里得到异常
                Console.WriteLine("Exception!");
                throw;
            }

        }
        static void go()
        {
            throw null;
        }

这里try/catch语句一点用也没有,新创建的线程将引发NullReferenceException异常。当你考虑到每个线程有独立的执行路径的时候,便知道这行为是有道理的

补救方法是在线程处理的方法中加入自己的异常处理:

        public static void Main()
        {
            new Thread(Go).Start();
        }

        static void Go()
        {
            try
            {
                throw null;
                //这个异常在下面会被捕捉到...
            }
            catch (Exception ex)
            {
                //记录异常日志,并且或通知另一个线程 我们发生错误
            }
        }

从.NET 2.0开始,任何线程内的未处理的异常都将导致整个程序关闭,这意味着忽略异常不再是一个选项了。因此为了避免由未处理异常引起的程序崩溃,try/catch块需要出现在每个线程进入的方法内,至少要在产品程序中应该如此。对于经常使用“全局”异常处理的Windows Forms程序员来说,这可能有点麻烦,像下面这样:

using System;
using System.Threading;
using System.Windows.Forms;
  
static class Program
 {
  static void Main()
 {
    Application.ThreadException+= HandleError;
    Application.Run (new MainForm());
  }
  
  static void HandleError(object sender,ThreadExceptionEventArgs e) 
{
    记录异常或者退出程序或者继续运行...
  }
}

Application.ThreadException事件在异常被抛出时触发,以一个Windows信息(比如:键盘,鼠标活着 "paint" 等信息)的方式,简言之,一个Windows Forms程序的几乎所有代码。虽然这看起来很完美,它使人产生一种虚假的安全感——所有的异常都被中央异常处理捕捉到了。由工作线程抛出的异常便是一个没有被Application.ThreadException捕捉到的很好的例外。(在Main方法中的代码,包括构造器的形式,在Windows信息开始前先执行)

.NET framework为全局异常处理提供了一个更低级别的事件:AppDomain.UnhandledException,这个事件在任何类型的程序(有或没有用户界面)的任何线程有任何未处理的异常触发。尽管它提供了好的不得已的异常处理解决机制,但是这不意味着这能保证程序不崩溃,也不意味着能取消.NET异常对话框。

在产品程序中,明确地使用异常处理在所有线程进入的方法中是必要的,可以使用包装类和帮助类来分解工作来完成任务,比如使用BackgroundWorker类(在第三部分进行讨论)

原文地址:https://www.cnblogs.com/yuejian/p/10930567.html