深入浅出多线程系列之二:关于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,

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






本文转自LoveJenny博客园博客,原文链接:http://www.cnblogs.com/LoveJenny/archive/2011/05/21/2052556.html,如需转载请自行联系原作者
目录
相关文章
|
3月前
|
Java 开发者
Java面试题:请解释内存泄漏的原因,并说明如何使用Thread类和ExecutorService实现多线程编程,请解释CountDownLatch和CyclicBarrier在并发编程中的用途和区别
Java面试题:请解释内存泄漏的原因,并说明如何使用Thread类和ExecutorService实现多线程编程,请解释CountDownLatch和CyclicBarrier在并发编程中的用途和区别
41 0
|
2月前
|
Java 开发者
奇迹时刻!探索 Java 多线程的奇幻之旅:Thread 类和 Runnable 接口的惊人对决
【8月更文挑战第13天】Java的多线程特性能显著提升程序性能与响应性。本文通过示例代码详细解析了两种核心实现方式:Thread类与Runnable接口。Thread类适用于简单场景,直接定义线程行为;Runnable接口则更适合复杂的项目结构,尤其在需要继承其他类时,能保持代码的清晰与模块化。理解两者差异有助于开发者在实际应用中做出合理选择,构建高效稳定的多线程程序。
49 7
|
1月前
|
存储 Java 程序员
优化Java多线程应用:是创建Thread对象直接调用start()方法?还是用个变量调用?
这篇文章探讨了Java中两种创建和启动线程的方法,并分析了它们的区别。作者建议直接调用 `Thread` 对象的 `start()` 方法,而非保持强引用,以避免内存泄漏、简化线程生命周期管理,并减少不必要的线程控制。文章详细解释了这种方法在使用 `ThreadLocal` 时的优势,并提供了代码示例。作者洛小豆,文章来源于稀土掘金。
【多线程面试题 二】、 说说Thread类的常用方法
Thread类的常用方法包括构造方法(如Thread()、Thread(Runnable target)等)、静态方法(如currentThread()、sleep(long millis)、yield()等)和实例方法(如getId()、getName()、interrupt()、join()等),用于线程的创建、控制和管理。
|
2月前
|
SQL 机器学习/深度学习 算法
【python】python指南(一):线程Thread
【python】python指南(一):线程Thread
38 0
|
3月前
|
Java C# Python
线程等待(Thread Sleep)
线程等待(Thread Sleep)
|
4月前
|
存储 设计模式 安全
C++一分钟之-并发编程基础:线程与std::thread
【6月更文挑战第26天】C++11的`std::thread`简化了多线程编程,允许并发执行任务以提升效率。文中介绍了创建线程的基本方法,包括使用函数和lambda表达式,并强调了数据竞争、线程生命周期管理及异常安全等关键问题。通过示例展示了如何用互斥锁避免数据竞争,还提及了线程属性定制、线程局部存储和同步工具。理解并发编程的挑战与解决方案是提升程序性能的关键。
71 3
|
4月前
|
Java
Java中,有两种主要的方式来创建和管理线程:`Thread`类和`Runnable`接口。
【6月更文挑战第24天】Java创建线程有两种方式:`Thread`类和`Runnable`接口。`Thread`直接继承受限于单继承,适合简单情况;`Runnable`实现接口可多继承,利于资源共享和任务复用。推荐使用`Runnable`以提高灵活性。启动线程需调用`start()`,`Thread`直接启动,`Runnable`需通过`Thread`实例启动。根据项目需求选择适当方式。
51 2
|
4月前
|
Java 开发者
JAVA多线程初学者必看:为何选择继承Thread还是Runnable,这其中有何玄机?
【6月更文挑战第19天】在Java中创建线程,可选择继承Thread类或实现Runnable接口。继承Thread直接运行,但限制了多重继承;实现Runnable更灵活,允许多线程共享资源且利于代码组织。推荐实现Runnable接口,以保持类的继承灵活性和更好的资源管理。
52 2
|
4月前
|
Java 开发者
告别单线程时代!Java 多线程入门:选继承 Thread 还是 Runnable?
【6月更文挑战第19天】在Java中,面对多任务需求时,开发者可以选择继承`Thread`或实现`Runnable`接口来创建线程。`Thread`继承直接但限制了单继承,而`Runnable`接口提供多实现的灵活性和资源共享。多线程能提升CPU利用率,适用于并发处理和提高响应速度,如在网络服务器中并发处理请求,增强程序性能。不论是选择哪种方式,都是迈向高效编程的重要一步。
37 2