浅谈C#多线程简单例子讲解_C#教程

.NET将关于多线程的功能定义在System.Threading名字空间中。因此,要使用多线程,必须先声明引用此名字空间(using System.Threading;)。

a.启动线程

顾名思义,“启动线程”就是新建并启动一个线程的意思,如下代码可实现:

Thread thread1 = new Thread(new ThreadStart( Count));

其中的 Count 是将要被新线程执行的函数。

b.杀死线程

“杀死线程”就是将一线程斩草除根,为了不白费力气,在杀死一个线程前最好先判断它是否还活着(通过 IsAlive 属性),然后就可以调用 Abort 方法来杀死此线程。

c.暂停线程

它的意思就是让一个正在运行的线程休眠一段时间。如 thread.Sleep(1000); 就是让线程休眠1秒钟。

d.优先级

这个用不着解释了。Thread类中hreadPRiority属性,它用来设置优先级,但不能保证操作系统会接受该优先级。一个线程的优先级可分为5种:Normal, AboveNormal, BelowNormal, Highest, Lowest。具体实现例子如下:

thread.Priority = ThreadPriority.Highest;

e.挂起线程

Thread类的Suspend方法用来挂起线程,直到调用Resume,此线程才可以继续执行。如果线程已经挂起,那就不会起作用。

if (thread.ThreadState = ThreadState.Running)
{
thread.Suspend();
}

f.恢复线程

用来恢复已经挂起的线程,以让它继续执行,如果线程没挂起,也不会起作用。

if (thread.ThreadState = ThreadState.Suspended)
{
thread.Resume();
}

下面将列出一个例子,以说明简单的线程处理功能。此例子来自于帮助文档。

using System;
using System.Threading;

// Simple threading scenario: Start a static method running
// on a second thread.
public class ThreadExample {
// The ThreadProc method is called when the thread starts.
// It loops ten times, writing to the console and yielding
// the rest of its time slice each time, and then ends.
public static void ThreadProc() {
for (int i = 0; i < 10; i++) {
Console.WriteLine("ThreadProc: {0}", i);
// Yield the rest of the time slice.
Thread.Sleep(0);
}
}

public static void Main() {
Console.WriteLine("Main thread: Start a second thread.");
// The constructor for the Thread class requires a ThreadStart
// delegate that represents the method to be executed on the
// thread. C# simplifies the creation of this delegate.
Thread t = new Thread(new ThreadStart(ThreadProc));
// Start ThreadProc. On a uniprocessor, the thread does not get
// any processor time until the main thread yields. Uncomment
// the Thread.Sleep that follows t.Start() to see the difference.
t.Start();
//Thread.Sleep(0);

for (int i = 0; i < 4; i++) {
Console.WriteLine("Main thread: Do some work.");
Thread.Sleep(0);
}

Console.WriteLine("Main thread: Call Join(), to wait until ThreadProc ends.");
t.Join();
Console.WriteLine("Main thread: ThreadProc.Join has returned. Press Enter to end program.");
Console.ReadLine();
}
}

此代码产生的输出类似如下内容:

Main thread: Start a second thread.
Main thread: Do some work.
ThreadProc: 0
Main thread: Do some work.
ThreadProc: 1
Main thread: Do some work.
ThreadProc: 2
Main thread: Do some work.
ThreadProc: 3
Main thread: Call Join(), to wait until ThreadProc ends.
ThreadProc: 4
ThreadProc: 5
ThreadProc: 6
ThreadProc: 7
ThreadProc: 8
ThreadProc: 9
Main thread: ThreadProc.Join has returned. Press Enter to end program.

在Visul C#中System.Threading 命名空间提供一些使得可以进行多线程编程的类和接口,其中线程的创建有以下三种方法:Thread、ThreadPool、Timer。下面就它们的使用方法逐个作一简单介绍。

一、Thread

这也许是最复杂的方法,但它提供了对线程的各种灵活控制。首先你必须使用它的构造函数创建一个线程实例,它的参数比较简单,只有一个ThreadStart 委托:public Thread(ThreadStart start);然后调用Start()启动它,当然你可以利用它的Priority属性来设置或获得它的运行优先级(enum ThreadPriority: Normal、 Lowest、 Highest、 BelowNormal、 AboveNormal)。

下例首先生成了两个线程实例t1和t2,然后分别设置它们的优先级,接着启动两线程(两线程基本一样,只不过它们输出不一样,t1为“1”,t2为“2”,根据它们各自输出字符个数比可大致看出它们占用CPU时间之比,这也反映出了它们各自的优先级)。

static void Main(string[] args)
{
 Thread t1 = new Thread(new ThreadStart(Thread1));
 Thread t2 = new Thread(new ThreadStart(Thread2));

 t1.Priority = ThreadPriority.BelowNormal ;
 t2.Priority = ThreadPriority.Lowest ;
   t1.Start();
  t2.Start();
 }
public static void Thread1()
{
 for (int i = 1; i < 1000; i++)
 {//每运行一个循环就写一个“1”
  dosth();
 Console.Write("1");
 }
 }
public static void Thread2()
{
 for (int i = 0; i < 1000; i++)
 {//每运行一个循环就写一个“2”
 dosth();
 Console.Write("2");
 }
}
public static void dosth()
{//用来模拟复杂运算
 for (int j = 0; j < 10000000; j++)
 {
 int a=15;
 a = a*a*a*a;
 }
}

以上程序运行结果为:

11111111111111111111111111111111111111111121111111111111111111111111111111111111111112
11111111111111111111111111111111111111111121111111111111111111111111111111111111111112
11111111111111111111111111111111111111111121111111111111111111111111111111111111111112

从以上结果我们可以看出,t1线程所占用CPU的时间远比t2的多,这是因为t1的优先级比t2的高,若我们把t1和t2的优先级都设为Normal,结果见下:

121211221212121212121212121212121212121212121212121212121212121212121
212121212121212121212121212121212121212121212121212121212121212121212
121212121212121212

从上例我们可看出,它的构造类似于win32的工作线程,但更加简单,只需把线程要调用的函数作为委托,然后把委托作为参数构造线程实例即可。当调用Start()启动后,便会调用相应的函数,从那函数第一行开始执行。

接下来我们结合线程的ThreadState属性来了解线程的控制。ThreadState是一个枚举类型,它反映的是线程所处的状态。当一个Thread实例刚创建时,它的ThreadState是Unstarted;当此线程被调用Start()启动之后,它的ThreadState是 Running;  在此线程启动之后,如果想让它暂停(阻塞),可以调用Thread.Sleep() 方法,它有两个重载方法(Sleep(int )、Sleep(Timespan )),只不过是表示时间量的格式不同而已,当在某线程内调用此函数时,它表示此线程将阻塞一段时间(时间是由传递给 Sleep 的毫秒数或Timespan决定的,但若参数为0则表示挂起此线程以使其它线程能够执行,指定 Infinite 以无限期阻塞线程),此时它的ThreadState将变为WaitSleepJoin,另外值得注意一点的是Sleep()函数被定义为了static?! 这也意味着它不能和某个线程实例结合起来用,也即不存在类似于t1.Sleep(10)的调用!正是如此,Sleep()函数只能由需“Sleep”的线程自己调用,不允许其它线程调用,正如when to Sleep是个人私事不能由它人决定。但是当某线程处于WaitSleepJoin状态而又不得不唤醒它时,可使用Thread.Interrupt 方法 ,它将在线程上引发ThreadInterruptedException,下面我们先看一个例子(注意Sleep的调用方法):

static void Main(string[] args)
{
 Thread t1 = new Thread(new ThreadStart(Thread1));
 t1.Start();
  t1.Interrupt ();
  E.WaitOne ();
  t1.Interrupt ();
   t1.Join();
   Console.WriteLine(“t1 is end”);
}
static AutoResetEvent E = new AutoResetEvent(false);
public static void Thread1()
{
 try
 {//从参数可看出将导致休眠
 Thread.Sleep(Timeout.Infinite);
 }
 catch(System.Threading.ThreadInterruptedException e)
 {//中断处理程序
 Console.WriteLine (" 1st interrupt");
 }
  E.Set ();
 try
 {// 休眠
 Thread.Sleep(Timeout.Infinite );
 }
 catch(System.Threading.ThreadInterruptedException e)
 {
  Console.WriteLine (" 2nd interrupt");
 }//暂停10秒
   Thread.Sleep (10000);
  }

运行结果为:

1st interrupt
 2nd interrupt
 (10s后)t1 is end

从上例我们可以看出Thread.Interrupt方法可以把程序从某个阻塞(WaitSleepJoin)状态唤醒进入对应的中断处理程序,然后继续往下执行(它的ThreadState也变为Running),此函数的使用必须注意以下几点:

1、此方法不仅可唤醒由Sleep导致的阻塞,而且对一切可导致线程进入WaitSleepJoin状态的方法(如Wait和Join)都有效。如上例所示, 使用时要把导致线程阻塞的方法放入try块内, 并把相应的中断处理程序放入catch块内。

2、对某一线程调用Interrupt, 如它正处于WaitSleepJoin状态, 则进入相应的中断处理程序执行, 若此时它不处于WaitSleepJoin状态, 则它后来进入此状态时, 将被立即中断。若在中断前调用几次Interrupt, 只有第一次调用有效, 这正是上例我用同步的原因, 这样才能确保第二次调用Interrupt在第一个中断后调用,否则的话可能导致第二次调用无效(若它在第一个中断前调用)。你可以把同步去掉试试,其结果很可能是:   1st interrupt

上例还用了另外两个使线程进入WaitSleepJoin状态的方法:利用同步对象和Thread.Join方法。Join方法的使用比较简单,它表示在调用此方法的当前线程阻塞直至另一线程(此例中是t1)终止或者经过了指定的时间为止(若它还带了时间量参数),当两个条件(若有)任一出现,它立即结束WaitSleepJoin状态进入Running状态(可根据.Join方法的返回值判断为何种条件,为true,则是线程终止;false则是时间到)。线程的暂停还可用Thread.Suspend方法,当某线程处于Running状态时对它调用Suspend方法,它将进入SuspendRequested状态,但它并不会被立即挂起,直到线程到达安全点之后它才可以将该线程挂起,此时它将进入Suspended状态。如对一个已处于Suspended的线程调用则无效,要恢复运行只需调用Thread.Resume即可。

最后我们谈的是线程的销毁,我们可以对需销毁的线程调用Abort方法,它会在此线程上引发ThreadAbortException。我们可把线程内的一些代码放入try块内,并把相应处理代码放入相应的catch块内,当线程正执行try块内代码时如被调用Abort,它便会跳入相应的catch块内执行,执行完catch快内的代码后它将终止(若catch块内执行了ResetAbort则不同了:它将取消当前Abort请求,继续向下执行。所以如要确保某线程终止的最好用Join,如上例)。

二、ThreadPool

线程池(ThreadPool)是一种相对较简单的方法,它适应于一些需要多个线程而又较短任务(如一些常处于阻塞状态的线程) ,它的缺点是对创建的线程不能加以控制,也不能设置其优先级。由于每个进程只有一个线程池,当然每个应用程序域也只有一个线程池(对线),所以你将发现ThreadPool类的成员函数都为static! 当你首次调用ThreadPool.QueueUserWorkItem、ThreadPool.RegisterWaitForSingleObject等,便会创建线程池实例。下面就线程池当中的两函数作一介绍:

public static bool QueueUserWorkItem( //调用成功则返回true
WaitCallback callBack,//要创建的线程调用的委托
  object state //传递给委托的参数
)//它的另一个重载函数类似,只是委托不带参数而已

此函数的作用是把要创建的线程排队到线程池,当线程池的可用线程数不为零时(线程池有创建线程数的限制,缺身值为25),便创建此线程,否则就排队到线程池等到它有可用的线程时才创建。

public static RegisteredWaitHandle RegisterWaitForSingleObject(
 WaitHandle waitObject,// 要注册的 WaitHandle
 WaitOrTimerCallback callBack,// 线程调用的委托
 object state,//传递给委托的参数
 int TimeOut,//超时,单位为毫秒,
 bool executeOnlyOnce file://是否只执行一次
);
public delegate void WaitOrTimerCallback(
 object state,//也即传递给委托的参数
 bool timedOut//true表示由于超时调用,反之则因为waitObject
);

此函数的作用是创建一个等待线程,一旦调用此函数便创建此线程,在参数waitObject变为终止状态或所设定的时间TimeOut到了之前,它都处于“阻塞”状态,值得注意的一点是此“阻塞”与Thread的WaitSleepJoin状态有很大的不同:当某Thread处于WaitSleepJoin状态时CPU会定期的唤醒它以轮询更新状态信息,然后再次进入WaitSleepJoin状态,线程的切换可是很费资源的;而用此函数创建的线程则不同,在触发它运行之前,CPU不会切换到此线程,它既不占用CPU的时间又不浪费线程切换时间,但CPU又如何知道何时运行它?实际上线程池会生成一些辅助线程用来监视这些触发条件,一旦达到条件便启动相应的线程,当然这些辅助线程本身也占用时间,但是如果你需创建较多的等待线程时,使用线程池的优势就越加明显。见下例:

static AutoResetEvent ev=new AutoResetEvent(false);
public static int Main(string[] args)
{ ThreadPool.RegisterWaitForSingleObject(
  ev,
  new WaitOrTimerCallback(WaitThreadFunc),
  4,
  2000,
  false//表示每次完成等待操作后都重置计时器,直到注销等待
  );
ThreadPool.QueueUserWorkItem (new WaitCallback (ThreadFunc),8);
Thread.Sleep (10000);
 return 0;
}
 public static void ThreadFunc(object b)
{ Console.WriteLine ("the object is {0}",b);
for(int i=0;i<2;i++)
{ Thread.Sleep (1000);
 ev.Set();
}
}
public static void WaitThreadFunc(object b,bool t)
{ Console.WriteLine ("the object is {0},t is {1}",b,t);
 }

其运行结果为:

the object is 8
the object is 4,t is False
the object is 4,t is False
the object is 4,t is True
the object is 4,t is True
the object is 4,t is True

从以上结果我们可以看出线程ThreadFunc运行了1次,而WaitThreadFunc运行了5次。我们可以从WaitOrTimerCallback中的bool t参数判断启动此线程的原因:t为false,则表示由于waitObject,否则则是由于超时。另外我们也可以通过object b向线程传递一些参数。

3、Timer

它适用于需周期性调用的方法,它不在创建计时器的线程中运行,它在由系统自动分配的单独线程中运行。这和Win32中的SetTimer方法类似。它的构造为:

public Timer(
 TimerCallback callback,//所需调用的方法
 object state,//传递给callback的参数
 int dueTime,//多久后开始调用callback
 int period//调用此方法的时间间隔
); 

// 如果 dueTime 为0,则 callback 立即执行它的首次调用。如果 dueTime 为 Infinite,则 callback 不调用它的方法。计时器被禁用,但使用 Change 方法可以重新启用它。如果 period 为0或 Infinite,并且 dueTime 不为 Infinite,则 callback 调用它的方法一次。计时器的定期行为被禁用,但使用 Change 方法可以重新启用它。如果 period 为零 (0) 或 Infinite,并且 dueTime 不为 Infinite,则 callback 调用它的方法一次。计时器的定期行为被禁用,但使用 Change 方法可以重新启用它。

在创建计时器之后若想改变它的period和dueTime,我们可以通过调用Timer的Change方法来改变:

public bool Change(
 int dueTime,
 int period
);//显然所改变的两个参数对应于Timer中的两参数
public static int Main(string[] args)
{
Console.WriteLine ("period is 1000");
Timer tm=new Timer (new TimerCallback (TimerCall),3,1000,1000);
Thread.Sleep (2000);
Console.WriteLine ("period is 500");
tm.Change (0,800);
Thread.Sleep (3000);
return 0;
 }
public static void TimerCall(object b)
{
Console.WriteLine ("timercallback; b is {0}",b);
}

其运行结果为:

period is 1000
timercallback;b is 3
timercallback;b is 3
period is 500
timercallback;b is 3
timercallback;b is 3
timercallback;b is 3
timercallback;b is 3 

总结

从以上的简单介绍,我们可以看出它们各自使用的场合:Thread适用于那些需对线程进行复杂控制的场合;ThreadPool适应于一些需要多个线程而又较短任务(如一些常处于阻塞状态的线程);Timer则适用于那些需周期性调用的方法。只要我们了解了它们的使用特点,我们就可以很好的选择合适的方法。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索c#
多线程例子
浅谈艺术作品的分类、浅谈党性修养、浅谈信息化战争、浅谈小班化教学的优势、浅谈 英文,以便于您获取更多的相关知识。

时间: 2024-12-02 17:45:47

浅谈C#多线程简单例子讲解_C#教程的相关文章

浅谈C# 中的委托和事件_C#教程

引言 委托 和 事件在 .Net Framework中的应用非常广泛,然而,较好地理解委托和事件对很多接触C#时间不长的人来说并不容易.它们就像是一道槛儿,过了这个槛的人,觉得真是太容易了,而没有过去的人每次见到委托和事件就觉得心里别(biè)得慌,混身不自在.本文中,我将通过两个范例由浅入深地讲述什么是委托.为什么要使用委托.事件的由来..Net Framework中的委托和事件.委托和事件对Observer设计模式的意义,对它们的中间代码也做了讨论. 将方法作为方法的参数 我们先不管这个标题

浅谈C#中正则表达式的使用_C#教程

目前为止,许多编程语言和工具都包含对正则表达式的支持,C#也不例外,C#基础类库中包含有一个命名空间(System.Text.RegularExpressions)和一系列可以充分发挥规则表达式威力的类(Regex.Match.Group等).那么,什么是正则表达式,怎么定义正则表达式呢? 一.正则表达式基础 什么是正则表达式 在编写字符串的处理程序时,经常会有查找符合某些复杂规则的字符串的需要.正则表达式就是用于描述这些规则的工具.换句话说,正则表达式就是记录文本规则的代码. 通常,我们在使用

鱼还是熊掌:浅谈多进程多线程的选择

          鱼还是熊掌:浅谈多进程多线程的选择 关于多进程和多线程,教科书上最经典的一句话是"进程是资源分配的最小单位,线程是CPU调度的最小单位",这句话应付考试基本上够了,但如果在工作中遇到类似的选择问题,那就没有这么简单了,选的不好,会让你深受其害.   经常在网络上看到有的XDJM问"多进程好还是多线程好?"."Linux下用多进程还是多线程?"等等期望一劳永逸的问题,我只能说:没有最好,只有更好.根据实际情况来判断,哪个更加合适

浅谈Java多线程编程中Boolean常量的同步问题_java

在JAVA中通过synchronized语句可以实现多线程并发.使用同步代码块,JVM保证同一时间只有一个线程可以拥有某一对象的锁.锁机制实现了多个线程安全地对临界资源进行访问.   同步代码写法如下:   代码1: Object obj = new Object(); ... synchronized(obj) { //TODO: 访问临界资源 } JAVA的多线程总是充满陷阱,如果我们用Boolean作为被同步的对象,可能会出现以下两种情况:   一. 以为对一个对象加锁,实际同步的是不同对

浅谈C语言的字符串分割_C 语言

说起来很有意思,自认为对C语言理解得还是比较深刻的.但居然到今天才知道有个strtok函数,试用了一下突然感慨以前做了多少重复劳动.每次需要解析配置文件,每次需要分割字符串,居然都是自己去分割字符串,既累人又容易出错.感概技术学得不够全面啊!这里引用一段strtok用法: The strtok() function returns a pointer to the next "token" in str1, where str2 contains the delimiters that

浅谈c++中的输入输出方法_C 语言

cin:  当碰到空格或换行符'\n'时,输入结束: char a[10],b[10]; cout<<"Enter some input:\n"; cin>>a>>b; cout<<a<<b<<"End\n"; 输出结果为: Enter some input: 12 34 56 1234END cin.getline: 当碰到换行符'\n'或达到所能接受的最大字符数时,输入结束: char a[

C#使用读写锁三行代码简单解决多线程并发的问题_C#教程

在开发程序的过程中,难免少不了写入错误日志这个关键功能.实现这个功能,可以选择使用第三方日志插件,也可以选择使用数据库,还可以自己写个简单的方法把错误信息记录到日志文件. 选择最后一种方法实现的时候,若对文件操作与线程同步不熟悉,问题就有可能出现了,因为同一个文件并不允许多个线程同时写入,否则会提示"文件正在由另一进程使用,因此该进程无法访问此文件". 这是文件的并发写入问题,就需要用到线程同步.而微软也给线程同步提供了一些相关的类可以达到这样的目的,本文使用到的 System.Thr

浅谈单调队列、单调栈_C 语言

初谈这个话题,相信许多人会有一种似有所悟,但又不敢确定的感觉.没错,这正是因为其中"单调"一词的存在,所谓单调是什么,学过函数的people都知道单调函数或者函数的单调性,直白一点说单调就是一直增或一直减.例如:1,3,5,9就是一个单调增数列,数列中不存在后一个数比前一个数小的现象.那么同样,在这里谈到的话题也有类似特点. 先说一下单调队列吧!      单调队列,就是一个符合单调性质的队列,它同时具有单调的性质以及队列的性质.他在编程中使用频率不高,但却占有至关重要的地位.它的作用

浅谈三步简单判断网站的流量是否有质量

网站中存在着垃圾流量和质量流量两种,后者对于网站排名权重相当的有帮助,因为通过质量流量可以让搜索引挚判断网站的用户体验很好,自然对于用户体验好的网站搜索引挚也是给予一定的照顾的.而且从另一方面来说,网站中拥有高质量的流量其实就是反应出用户对该网站有一定的喜欢程度.但是网站既然有质量流量之分,当然也存在一些垃圾流量,垃圾流量的作用刚好与质量流量相反.所以,今天笔者谈谈怎么判断网站的流量是否有质量: 一.网站IP和PV值的比例 站长判断网站流量的质量时,最简单的当然就是看网站统计工具的IP和PV的比