zoukankan      html  css  js  c++  java
  • 深入浅出多线程系列之二:关于Thread的那些事

    1:你可以调用线程的实例方法Join来等待一个线程的结束。例如:

            public static void MainThread()
            {
                Thread t 
    = new Thread(Go);
                t.Start();
                t.Join();
                Console.WriteLine(
    "Thread t has ended!");
            }

            
    static void Go()
            {
                
    for (int i = 0; i < 1000; i++
                    Console.Write(
    "y");
            }

    在打印了1000Y之后,后面就会输出”Thread t has ended!”.,

    你可以在调用Join方法的时候给它一个timeout的参数,例如要超时一秒:

    t.Join(1000);
    t.Join(TimeSpan.FromSeconds(
    1));

     

    2:为线程传递参数

    为线程传递参数的最简单的方法莫过于执行一个lambda表达式,然后在方法里面给参数了,例如:

            static void Main()
            {
                Thread t 
    = new Thread(() => Print("Hello from t!"));
                t.Start();
            }

            
    static void Print(string message)
            {
                Console.WriteLine(message);
            }

    使用这种方法,你可以传递任何参数。

    当然Thread的构造函数中有一个传递参数的版本,你也可以使用下面的代码来传递参数:

            static void Main()
            {
                Thread t 
    = new Thread(Print);
                t.Start(
    "Hello from t!");
            }

            
    static void Print(object messageObj)
            {
                
    string message = (string)messageObj;
                Console.WriteLine(message);
            }

    这里有一点要注意,因为Print的方法签名必须匹配 ParameterizedThreadStart委托,

    ParameterizedThreadStart的参数是object,所以Print的参数必须也是object,所以在Print方法中必须进行强制转换。

    3:Lambda和捕获的变量。

    考虑下下面的代码:

                for (int i = 0; i < 10; i++)
                {
                    
    new Thread(() => Console.Write(i)).Start();
                }

    实际的输出是不确定的,例如可能是0113348899.

    为什么???

    关键的问题是局部变量ifor循环中指向的是相同的内存地址.因此,每一次都在一个运行时会被改变值的变量(i)上调用Console.Write方法,foreach中也存在相同问题。

    解决这个问题的方法很简单,例如使用一个临时的变量。例如:

                for (int i = 0; i < 10; i++)
                {
                    
    int temp = i;
                    
    new Thread(() => Console.Write(temp)).Start();
                }

    因为i是值类型,所以int temp=I 会复制i的值给temp,而在for循环中temp变量都是不同的,所以可以解决这个问题。

    下面的也是同样的道理:

                for (int i = 0; i < 10; i++)
                {
                    
    new Thread((obj) => Console.Write(obj)).Start(i); //因为每一个线程的obj都是不同的。
                }

    下面的例子可能更明显一些:

                string text = "t1";
                Thread t1 
    = new Thread(() => Console.WriteLine(text));
                text 
    = "t2";
                Thread t2 
    = new Thread(() => Console.WriteLine(text));

                t1.Start();
                t2.Start();

    因为两个lambda表达式捕获的是相同的text变量,所以 “t2”会被打印两次。

    output:

    t2

    t2 

    4:命名线程:

    给每一个线程一个合适的名字对于调试来说是有利的,尤其是在Visiaul Studio中,因为在线程窗口和调试位置工具栏中都会显示线程的名字,

    但是你只能设置一次线程的名字,尝试在以后更改名字会抛出一个异常,为变量命名的使用的是Name属性,例如:

    Thread worker=new Thread(Go);
    worker.Name
    =”worker”;
    worker.Name
    =”worker”; //会抛出异常

    5 :前台线程和后台线程:

    默认你显示创建的线程都是前台线程。

    只要前台线程有一个还在运行,应用程序就不会结束。

    只有所有的前台线程都结束了,应用程序才会结束,

    在应用程序结束的时候,所有后台线程都会被终止。

    你可以通过线程的IsBackground属性来查询和更改线程的状态。这里是一个例子。

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

    如果args.Length>0,则worker就是后台线程,那么应用程序会立即终止。

    否则worker 默认就是前台线程,所以只有在Console.ReadLine()方法结束后,应用程序才会终止。

    所以当你有时候发现关闭了应用程序窗口,但是在任务管理器中应用程序仍然在运行,很有可能就是还有一些前台线程在运行。

    6: 线程的优先级:

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

    只有在多个线程的环境下,线程优先级才有用。

    把一个线程的优先级提高并不会提高实时系统的性能,因为进程的优先级才是应用程序的瓶颈。为了更好的实时工作,你必须提高进程的优先级。

    例如process.PriorityClass=ProcessPriorityClass.High.

    7: 异常捕获:

    尝试在一个线程中捕获另一个线程的异常时失败的。例如:

           public static void Main()
            {
                
    try
                {
                    
    new Thread(Go).Start();
                }
                
    catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
    //永远运行不到这里.
                }
            }

            
    static void Go() { throw null;}

    我们在另一个线程中抛出了异常(throw null;),然后尝试在主线程中捕获它,我们永远都不到这个异常。

    为了在另一个线程中捕获异常,必须在那个线程上try,catch,finally.

    所以可以将代码改为下面的方式:

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

            
    static void Go()
            {
                
    try
                {
                    
    throw null;
                }
                
    catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }
            }

    8:全局捕获异常:

    Application.DispatcherUnhandledException 事件和Application.ThreadException 事件都只有在主UI线程中抛出异常的时候才会被触发。

    为了捕获所有的未处理的异常,你可以使用AppDomain.CurrentDomain.UnhandledException,

    虽然这个事件在任何未处理异常抛出的时候都会被触发,但是它不能让你阻止应用程序的关闭。

    参考资料:

    http://www.albahari.com/threading/

    CLR Via C# 3.0

  • 相关阅读:
    ConcurrentHashMap、Collections.synchronizedMap、Hashtable讨论(区别)java集合框架【3】 java1.5新特性
    struts2中方法拦截器(Interceptor)的中的excludeMethods与includeMethods的理解
    java抽象类详解以及与接口区别
    乐观锁理解
    URI和URL的区别
    JavaScript编程笔记
    2007年3月9日早上来到深圳……
    axWindowsMediaPlayer1控件循环播放方法
    C#中调用存储过程笔记(原)
    js日期选择控件(Asp.Net可用)
  • 原文地址:https://www.cnblogs.com/LoveJenny/p/2052556.html
Copyright © 2011-2022 走看看