c#线程之前台线程后台线程及使用
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了c#线程之前台线程后台线程及使用相关的知识,希望对你有一定的参考价值。
参考技术A 一、关于前台线程和后台线程1、简介
CLR中线程分为两种类型,一种是前台线程、另一种是后台线程.
前台线程:应用程序的主线程、Thread构造的线程都默认为前台线程
后台线程:线程池线程都为后台线程
2、区别
2.1 前台线程:前台线程一般执行重要性很高的任务,至于什么是重要性很高,这个需要结合业务综合考虑,哪些操作是当前应用程序必须执行的。
如下控制台程序:
控制台等待DoWork方法执行完成之后关闭。
2.2 后台线程:这里需要注意,当一个进程的所有前台线程关闭时,也就是当应用程序推出的时候,无论后台线程有没有执行完它的任务,它都会被强制关闭.但是,当应用程序开启时,它又会重新启动.后台线程一般执行不重要、耗时很短的任务,就算进程(应用程序)关闭了,导致它强制关闭,也不会造成影响的任务.比如系统清理程序等。
如下控制台程序:
控制台不等子线程处理完成,直接关闭。
注意:一般进程会在所有的前台线程执行完毕时关闭。
二、使用Thread构建异步操作(受限制)
1、Thread简介和使用场景
关于使用Thread类构建线程执行异步操作有以下几点需要注意的:
(1)、Thread本身微软并不建议使用,应为它其中的很多Api并不靠谱(如Start、Join、IsBackground等)
(2)、由于(1)的问题,所以微软将整个Thread类都不开放给Windows Store应用
虽然Thread有很多不好的缺点,但是它还是有它的用武之处,只要满足以下条件:
(1)、如果执行的代码处于一种特定的状态,这种状态对于线程池来说时非同寻常的。
(2)、线程需要以非普通优先级运行.所有线程池线程都以普通优先级运行,虽然可以修改,但是在不同线程池之间,这种优先级无法持续。
(3)、需要线程变现为一个前台线程(什么是前台线程,上面有解释),防止应用程序在线程结束前就关闭了.这个后台线程无法做到(也就是线程池线程)
(4)、线程需要执行长时间的计算任务。
(5)、线程可能存在终止的情况,程序池线程不存在这种情况(它会一直执行).Thread类的Abort方法。
三、CLR线程池
1、进程和CLR的关系
一个进程可以只包含一个CLR,也可以包含多个CLR
2、CLR和AppDomain的关系
一个CLR可以包含多个AppDomain
3、CLR和线程池的关系
一个CLR只包含一个线程池
所以得出一个CLR下的多个AppDomain共享一个线程池和一个进程下的多个CLR拥有多个线程池的结论.注:多个线程池间的线程池相互不产生影响。
4、CLR和线程池和操作请求队列的关系
(1)、CLR第一次初始化时,线程池并没有线程,当应用程序调用异步代码执行一个方法时,会将该请求记录项加入到操作请求队列中,线程池的代码从这个队列中获取记录项,并派发给线程池线程,接着
线程池会创建线程,当然这里会有性能开销,但是当该线程执行完毕之后,线程池会回收这个线程,这里注意:线程池不会直接销毁这个线程,而是让它处于闲置状态.这样就不会产生额外的性能开销。
但是如果该线程如果长时间处于闲置状态,那么线程池会销毁它,关于这个时间的计算很复杂,各个CLR对它的定义各不相同。
(2)、当应用程序向线程池发起了多个请求,线程池会尝试用一个线程来处理你所有的请求,但是如果这个线程处理压力过大,那么它会开启一个新的线程来给它分担压力.以此类推。
(3)、线程池之包含了少量线程,因为如果线程太多,会增加性能开销,当然如果你升级了你电脑的cpu,线程池则会创建更多的线程.这个过程线程池会自动的去读取你得cpu核数信息,自动的去分配合适的线程数
合理地分配CPU资源.当应用程序的压力减轻,那么它会销毁不用的线程。
C#多线程编程(转)
一、使用线程的理由
1、可以使用线程将代码同其他代码隔离,提高应用程序的可靠性。
2、可以使用线程来简化编码。
3、可以使用线程来实现并发执行。
二、基本知识
1、进程与线程:进程作为操作系统执行程序的基本单位,拥有应用程序的资源,进程包含线程,进程的资源被线程共享,线程不拥有资源。
2、前台线程和后台线程:通过Thread类新建线程默认为前台线程。当所有前台线程关闭时,所有的后台线程也会被直接终止,不会抛出异常。
3、挂起(Suspend)和唤醒(Resume):由于线程的执行顺序和程序的执行情况不可预知,所以使用挂起和唤醒容易发生死锁的情况,在实际应用中应该尽量少用。
4、阻塞线程:Join,阻塞调用线程,直到该线程终止。
5、终止线程:Abort:抛出 ThreadAbortException 异常让线程终止,终止后的线程不可唤醒。Interrupt:抛出 ThreadInterruptException 异常让线程终止,通过捕获异常可以继续执行。
6、线程优先级:AboveNormal BelowNormal Highest Lowest Normal,默认为Normal。
三、线程的使用
线程函数通过委托传递,可以不带参数,也可以带参数(只能有一个参数),可以用一个类或结构体封装参数。
namespace Test
{
class Program
{
static void Main(string[] args)
{
Thread t1 = new Thread(new ThreadStart(TestMethod));
Thread t2 = new Thread(new ParameterizedThreadStart(TestMethod));
t1.IsBackground = true;
t2.IsBackground = true;
t1.Start();
t2.Start("hello");
Console.ReadKey();
}
public static void TestMethod()
{
Console.WriteLine("不带参数的线程函数");
}
public static void TestMethod(object data)
{
string datastr = data as string;
Console.WriteLine("带参数的线程函数,参数为:{0}", datastr);
}
}
}
四、线程池
由于线程的创建和销毁需要耗费一定的开销,过多的使用线程会造成内存资源的浪费,出于对性能的考虑,于是引入了线程池的概念。线程池维护一个请求队 列,线程池的代码从队列提取任务,然后委派给线程池的一个线程执行,线程执行完不会被立即销毁,这样既可以在后台执行任务,又可以减少线程创建和销毁所带 来的开销。
线程池线程默认为后台线程(IsBackground)。
namespace Test
{
class Program
{
static void Main(string[] args)
{
//将工作项加入到线程池队列中,这里可以传递一个线程参数
ThreadPool.QueueUserWorkItem(TestMethod, "Hello");
Console.ReadKey();
}
public static void TestMethod(object data)
{
string datastr = data as string;
Console.WriteLine(datastr);
}
}
}
五、Task类
使用ThreadPool的QueueUserWorkItem()方法发起一次异步的线程执行很简单,但是该方法最大的问题是没有一个内建的机制 让你知道操作什么时候完成,有没有一个内建的机制在操作完成后获得一个返回值。为此,可以使用System.Threading.Tasks中的Task 类。
构造一个Task<TResult>对象,并为泛型TResult参数传递一个操作的返回类型。
namespace Test
{
class Program
{
static void Main(string[] args)
{
Task<Int32> t = new Task<Int32>(n => Sum((Int32)n), 1000);
t.Start();
t.Wait();
Console.WriteLine(t.Result);
Console.ReadKey();
}
private static Int32 Sum(Int32 n)
{
Int32 sum = 0;
for (; n > 0; --n)
checked{ sum += n;} //结果太大,抛出异常
return sum;
}
}
}
一个任务完成时,自动启动一个新任务。
一个任务完成后,它可以启动另一个任务,下面重写了前面的代码,不阻塞任何线程。
namespace Test
{
class Program
{
static void Main(string[] args)
{
Task<Int32> t = new Task<Int32>(n => Sum((Int32)n), 1000);
t.Start();
//t.Wait();
Task cwt = t.ContinueWith(task => Console.WriteLine("The result is {0}",t.Result));
Console.ReadKey();
}
private static Int32 Sum(Int32 n)
{
Int32 sum = 0;
for (; n > 0; --n)
checked{ sum += n;} //结果溢出,抛出异常
return sum;
}
}
}
六、委托异步执行
委托的异步调用:BeginInvoke() 和 EndInvoke()
namespace Test
{
public delegate string MyDelegate(object data);
class Program
{
static void Main(string[] args)
{
MyDelegate mydelegate = new MyDelegate(TestMethod);
IAsyncResult result = mydelegate.BeginInvoke("Thread Param", TestCallback, "Callback Param");
//异步执行完成
string resultstr = mydelegate.EndInvoke(result);
}
//线程函数
public static string TestMethod(object data)
{
string datastr = data as string;
return datastr;
}
//异步回调函数
public static void TestCallback(IAsyncResult data)
{
Console.WriteLine(data.AsyncState);
}
}
}
七、线程同步
1)原子操作(Interlocked):所有方法都是执行一次原子读取或一次写入操作。
2)lock()语句:避免锁定public类型,否则实例将超出代码控制的范围,定义private对象来锁定。
3)Monitor实现线程同步
通过Monitor.Enter() 和 Monitor.Exit()实现排它锁的获取和释放,获取之后独占资源,不允许其他线程访问。
还有一个TryEnter方法,请求不到资源时不会阻塞等待,可以设置超时时间,获取不到直接返回false。
4)ReaderWriterLock
当对资源操作读多写少的时候,为了提高资源的利用率,让读操作锁为共享锁,多个线程可以并发读取资源,而写操作为独占锁,只允许一个线程操作。
5)事件(Event)类实现同步
事件类有两种状态,终止状态和非终止状态,终止状态时调用WaitOne可以请求成功,通过Set将时间状态设置为终止状态。
1)AutoResetEvent(自动重置事件)
2)ManualResetEvent(手动重置事件)
6)信号量(Semaphore)
信号量是由内核对象维护的int变量,为0时,线程阻塞,大于0时解除阻塞,当一个信号量上的等待线程解除阻塞后,信号量计数+1。
线程通过WaitOne将信号量减1,通过Release将信号量加1,使用很简单。
7)互斥体(Mutex)
独占资源,用法与Semaphore相似。
8)跨进程间的同步
通过设置同步对象的名称就可以实现系统级的同步,不同应用程序通过同步对象的名称识别不同同步对象。
以上是关于c#线程之前台线程后台线程及使用的主要内容,如果未能解决你的问题,请参考以下文章