C#中Thread类应用

原文地址:http://www.cnblogs.com/nokiaguy/archive/2008/07/16/1244746.html

一、             Thread类的基本用法

通过System.Threading.Thread类可以开始新的线程,并在线程堆栈中运行静态或实例方法。可以通过Thread类的的构造方法传递一个无参数,并且不返回值(返回void)的委托(ThreadStart),这个委托的定义如下:

[ComVisibleAttribute(true)]

public delegate void ThreadStart()

我们可以通过如下的方法来建立并运行一个线程。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace MyThread
{
    class Program
    {
        public static void myStaticThreadMethod()
        {
            Console.WriteLine("myStaticThreadMethod");
        }
        static void Main(string[] args)
        {
            Thread thread1 = new Thread(myStaticThreadMethod);
            thread1.Start();  // 只要使用Start方法,线程才会运行
        }
    }
}

    除了运行静态的方法,还可以在线程中运行实例方法,代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace MyThread
{
    class Program
    {
        public void myThreadMethod()
        {
            Console.WriteLine("myThreadMethod");
        }
        static void Main(string[] args)
        {
            Thread thread2 = new Thread(new Program().myThreadMethod);
            thread2.Start();
        }
    }
}

    如果读者的方法很简单,或出去某种目的,也可以通过匿名委托或Lambda表达式来为Thread的构造方法赋值,代码如下:

Thread thread3 = new Thread(delegate() { Console.WriteLine("匿名委托"); });
thread3.Start();

Thread thread4 = new Thread(( ) => { Console.WriteLine("Lambda表达式"); });
thread4.Start();

    其中Lambda表达式前面的( )表示没有参数。

    为了区分不同的线程,还可以为Thread类的Name属性赋值,代码如下:

Thread thread5 = new Thread(() => { Console.WriteLine(Thread.CurrentThread.Name); });
thread5.Name = "我的Lamdba";
thread5.Start();

    如果将上面thread1至thread5放到一起执行,由于系统对线程的调度不同,输出的结果是不定的,如图1是一种可能的输出结果。



                                                                 图1

二、 定义一个线程类

    我们可以将Thread类封装在一个MyThread类中,以使任何从MyThread继承的类都具有多线程能力。MyThread类的代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace MyThread
{
   abstract class MyThread
    {
       Thread thread = null;

       abstract public void run();    

        public void start()
        {
            if (thread == null)
                thread = new Thread(run);
            thread.Start();
        }
    }
}

    可以用下面的代码来使用MyThread类。

class NewThread : MyThread
{
      override public void run()
      {
          Console.WriteLine("使用MyThread建立并运行线程");
      }
  }

  static void Main(string[] args)
  {

      NewThread nt = new NewThread();
      nt.start();
  }

     我们还可以利用MyThread来为线程传递任意复杂的参数。详细内容见下节。

三、     为线程传递参数

Thread类有一个带参数的委托类型的重载形式。这个委托的定义如下:

[ComVisibleAttribute(false)]

public delegate void ParameterizedThreadStart(Object obj)

这个Thread类的构造方法的定义如下:

 
public Thread(ParameterizedThreadStart start);

下面的代码使用了这个带参数的委托向线程传递一个字符串参数:

public static void myStaticParamThreadMethod(Object obj)
{
    Console.WriteLine(obj);
}

static void Main(string[] args)
{
      Thread thread = new Thread(myStaticParamThreadMethod);
      thread.Start("通过委托的参数传值");
}

要注意的是,如果使用的是不带参数的委托,不能使用带参数的Start方法运行线程,否则系统会抛出异常。但使用带参数的委托,可以使用thread.Start()来运行线程,这时所传递的参数值为null。
    也可以定义一个类来传递参数值,如下面的代码如下:

class MyData
{
    private String d1;
    private int d2;
    public MyData(String d1, int d2)
    {
          this.d1 = d1;
          this.d2 = d2;
    }
    public void threadMethod()
    {
          Console.WriteLine(d1);
          Console.WriteLine(d2);
    }
}

MyData myData = new MyData("abcd",1234);
Thread thread = new Thread(myData.threadMethod);
thread.Start();

    如果使用在第二节定义的MyThread类,传递参数会显示更简单,代码如下:

class NewThread : MyThread
{
    private String p1;
    private int p2;
    public NewThread(String p1, int p2)
    {
        this.p1 = p1;
        this.p2 = p2;
    }

    override public void run()
    {
        Console.WriteLine(p1);
        Console.WriteLine(p2);
    }
}

NewThread newThread = new NewThread("hello world", 4321);
newThread.start();

四、     前台和后台线程
    使用Thread建立的线程默认情况下是前台线程,在进程中,只要有一个前台线程未退出,进程就不会终止。主线程就是一个前台线程。而后台线程不管线程是否结束,只要所有的前台线程都退出(包括正常退出和异常退出)后,进程就会自动终止。一般后台线程用于处理时间较短的任务,如在一个Web服务器中可以利用后台线程来处理客户端发过来的请求信息。而前台线程一般用于处理需要长时间等待的任务,如在Web服务器中的监听客户端请求的程序,或是定时对某些系统资源进行扫描的程序。下面的代码演示了前台和后台线程的区别。

public static void myStaticThreadMethod()
{
    Thread.Sleep(3000);
}

Thread thread = new Thread(myStaticThreadMethod);
// thread.IsBackground = true;
thread.Start();

    如果运行上面的代码,程序会等待3秒后退出,如果将注释去掉,将thread设成后台线程,则程序会立即退出。

    要注意的是,必须在调用Start方法之前设置线程的类型,否则一但线程运行,将无法改变其类型。

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

五、   判断多个线程是否都结束的两种方法

确定所有线程是否都完成了工作的方法有很多,如可以采用类似于对象计数器的方法,所谓对象计数器,就是一个对象被引用一次,这个计数器就加1,销毁引用就减1,如果引用数为0,则垃圾搜集器就会对这些引用数为0的对象进行回收。

方法一:线程计数器

线程也可以采用计数器的方法,即为所有需要监视的线程设一个线程计数器,每开始一个线程,在线程的执行方法中为这个计数器加1,如果某个线程结束(在线程执行方法的最后为这个计数器减1),为这个计数器减1。然后再开始一个线程,按着一定的时间间隔来监视这个计数器,如是棕个计数器为0,说明所有的线程都结束了。当然,也可以不用这个监视线程,而在每一个工作线程的最后(在为计数器减1的代码的后面)来监视这个计数器,也就是说,每一个工作线程在退出之前,还要负责检测这个计数器。使用这种方法不要忘了同步这个计数器变量啊,否则会产生意想不到的后果。

方法二:使用Thread.join方法

join方法只有在线程结束时才继续执行下面的语句。可以对每一个线程调用它的join方法,但要注意,这个调用要在另一个线程里,而不要在主线程,否则程序会被阻塞的。

    个人感觉这种方法比较好。
    线程计数器方法演示:

    class ThreadCounter : MyThread
    {
        private static int count = 0;
        private int ms;
        private static void increment()
        {
            lock (typeof(ThreadCounter))  // 必须同步计数器
            {
                count++;
            }
        }
        private static void decrease()
        {
            lock (typeof(ThreadCounter))
            {
                count--;
            }
        }
        private static int getCount()
        {
            lock (typeof(ThreadCounter))
            {
                return count;
            }
        }
        public ThreadCounter(int ms)
        {
            this.ms = ms;
        }
        override public void run()
        {
            increment();
            Thread.Sleep(ms);
            Console.WriteLine(ms.ToString()+"毫秒任务结束");
            decrease();
            if (getCount() == 0)
                Console.WriteLine("所有任务结束");
        }
    }


ThreadCounter counter1 = new ThreadCounter(3000);
ThreadCounter counter2 = new ThreadCounter(5000);
ThreadCounter counter3 = new ThreadCounter(7000);

counter1.start();
counter2.start();
counter3.start();

    上面的代码虽然在大多数的时候可以正常工作,但却存在一个隐患,就是如果某个线程,假设是counter1,在运行后,由于某些原因,其他的线程并未运行,在这种情况下,在counter1运行完后,仍然可以显示出“所有任务结束”的提示信息,但是counter2和counter3还并未运行。为了消除这个隐患,可以将increment方法从run中移除,将其放到ThreadCounter的构造方法中,在这时,increment方法中的lock也可以去掉了。代码如:

        public ThreadCounter(int ms)
        {
            this.ms = ms;
            increment();
        }

    运行上面的程序后,将显示如图2的结果。


                                                                 图2

使用Thread.join方法演示

private static void threadMethod(Object obj)
{
    Thread.Sleep(Int32.Parse(obj.ToString()));
    Console.WriteLine(obj + "毫秒任务结束");
}
private static void joinAllThread(object obj)
{
    Thread[] threads = obj as Thread[];
    foreach (Thread t in threads)
        t.Join();
    Console.WriteLine("所有的线程结束");
}

static void Main(string[] args)
{
    Thread thread1 = new Thread(threadMethod);
    Thread thread2 = new Thread(threadMethod);
    Thread thread3 = new Thread(threadMethod);

     thread1.Start(3000);
     thread2.Start(5000);
     thread3.Start(7000);

     Thread joinThread = new Thread(joinAllThread);
     joinThread.Start(new Thread[] { thread1, thread2, thread3 });

}

    在运行上面的代码后,将会得到和图2同样的运行结果。上述两种方法都没有线程数的限制,当然,仍然会受到操作系统和硬件资源的限制。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
第1章 进程、线程与网络协议  1.1 进程和线程  1.1.1 Process  1.1.2 Thread  1.1.3 在一个线程操作另一个线程的控件 1.2 IP地址与端口  1.2.1 TCP/IP  1.2.2 IPAddress与Dns  1.2.3 IPHostEntry  1.2.4 IPEndPoint  1.3 套接字  1.3.1 Socket  1.3.2 面向连接的套接字  1.3.3 无连接的套接字  1.4 网络流  习题  第2章 TCP应用编程  2.1 同步TCP应用编程  2.1.1 使用套接字发送和接收数据  2.1.2 使用NetworkStream对象发送和接收数据  2.1.3 TcpClient与TcpListener  2.1.4 解决TCP的无消息边界问题  2.2 利用同步TCP编写网络游戏  2.2.1 服务器端编程  2.2.2 客户端编程  2.3 异步TCP应用编程  2.3.1 EventWaitHandle  2.3.2 AsyncCallback委托  2.3.3 BeginAcceptTcpClient方法和EndAcceptTcpClient方法  2.3.4 BeginConnect方法和EndConnect方法  2.3.5 发送数据  2.3.6 接收数据  2.4 异步TCP聊天程序  2.4.1 服务器端设计  2.4.2 客户端设计  习题  第3章 UDP应用编程  3.1 UDP基础知识  3.2 UDP应用编程技术  3.2.1 UdpClient  3.2.2 发送和接收数据的方法  3.3 利用UDP进行广播和组播  3.3.1 通过Internet实现群发功能  3.3.2 在Internet上举行网络会议讨论  习题  第4章 P2P应用编程  4.1 P2P基础知识  4.2 P2P应用举例  习题  第5章 SMTP与POP3应用编程  5.1 通过应用程序发送电子邮件  5.1.1 SMTP  5.1.2 发送邮件  5.2 利用同步TCP接收电子邮件  5.2.1 POP3工作原理  5.2.2 邮件接收处理  习题  第6章 网络数据加密与解密  6.1 对称加密  6.2 不对称加密  6.3 通过网络传递加密数据 6.4 Hash算法与数字签名  习题  第7章 三维设计与多媒体编程  7.1 简单的3D设计入门  7.2 DirectX基础知识  7.2.1 左手坐标系与右手坐标系  7.2.2 设备  7.2.3 顶点与顶点缓冲  7.2.4 Mesh对象  7.2.5 法线  7.2.6 纹理与纹理映射  7.2.7 世界矩阵、投影矩阵与视图矩阵  7.2.8 背面剔除  7.3 Primitive  7.4 Mesh  7.5 灯光与材质  7.6 音频与视频  7.7 直接使用SoundPlayer播放WAV音频文件  习题
以下是一个简单的基于 TPL 的 C# 应用程序示例,它使用 Parallel.ForEach 方法并行处理一个字符串列表: ```csharp using System; using System.Collections.Generic; using System.Threading.Tasks; class Program { static void Main(string[] args) { List<string> list = new List<string> { "apple", "banana", "cherry", "date", "elderberry", "fig", "grape" }; Parallel.ForEach(list, item => { Console.WriteLine($"Processing item '{item}' on thread {Task.CurrentId}"); // 模拟处理时间 Task.Delay(1000).Wait(); }); Console.WriteLine("All items processed"); } } ``` 在上面的示例,我们首先创建了一个字符串列表 `list`,其包含七个元素。然后,我们使用 `Parallel.ForEach` 方法对列表的每个元素进行并行处理。在处理每个元素时,我们将元素的名称和当前线程的 ID 打印到控制台上,并使用 `Task.Delay` 方法模拟处理时间。最后,我们打印一条消息,表示所有元素都已处理完毕。 在运行上面的示例时,您应该会看到似于以下输出: ``` Processing item 'banana' on thread 3 Processing item 'apple' on thread 1 Processing item 'cherry' on thread 2 Processing item 'elderberry' on thread 4 Processing item 'fig' on thread 5 Processing item 'date' on thread 6 Processing item 'grape' on thread 7 All items processed ``` 可以看到,所有元素都在不同的线程上并行处理,处理时间也得到了模拟。请注意,由于 `Parallel.ForEach` 方法是异步的,因此程序可能会在所有元素都处理完毕之前结束。如果您希望等待所有任务完成后再继续执行程序,请使用 `Task.WaitAll` 方法等待所有任务完成。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值