线程

一. 线程概述.

默认情况下,C# 程序具有一个线程。此线程执行程序中以 Main 方法开始和结束的代码。Main 直接或间接执行的每一个命令都由默认线程(或主线程)执行,当 Main 返回时此线程也将终止。不过,可以创建辅助线程,以便与主线程一起并行执行代码。这些线程通常称为“辅助线程”。

辅助线程可以用于执行耗时较多的任务或时间要求紧迫的任务,而不必占用主线程。例如,辅助线程经常用在服务器应用程序中,以便无需等待前面的请求完成即可响应传入的请求。辅助线程还可用于在桌面应用程序中执行“后台”任务,以便使主线程(用于驱动用户界面元素)保持对用户操作的响应。

多线程处理解决了吞吐量和响应性的问题,但同时也带来了资源共享问题,如死锁和争用状态。多线程特别适用于需要不同资源(如文件句柄和网络连接)的任务。为单个资源分配多个线程可能会导致同步问题,线程会被频繁阻止以等待其他线程,从而与使用多线程的初衷背道而驰。

常见的策略是使用辅助线程执行不需要大量占用其他线程所使用的资源的、耗时较多的任务或时间要求紧迫的任务。实际上,程序中的某些资源必须由多个线程访问。考虑到这些情况,System.Threading 命名空间提供了用于同步线程的类。这些类包括 MutexMonitorInterlockedAutoResetEventManualResetEvent

您可以使用这些类中的部分或所有类来同步多个线程的活动,但是某些多线程处理支持由 C# 语言提供。例如,C# 中的 Lock 语句通过隐式使用 Monitor 来提供同步功能。

二.Threading

线程处理使 C# 程序能够执行并发处理,以便您可以同时执行多个操作。例如,您可以使用线程处理来监视用户输入,执行后台任务,以及处理并发输入流。System.Threading 支持多线程编程的类和接口,可以执行创建和启动新线程,同步多个线程,挂起线程以及中止线程等任务。

若要在 C# 代码中合并线程处理,请创建一个将在主线程外执行的函数,并让一个新的 Thread 对象指向该函数。下面的代码示例在 C# 应用程序中创建一个新线程:

隐藏行号 复制代码 这是一段程序代码。
  1. Thread newThread;
    
  2. newThread = new Thread(anObject.AMethod)  //1.创建一个线程
    
  3. newThread.Start();                        //2.启动一个线程
    

三.线程的同步.

在应用程序中使用多个线程的一个好处是每个线程都可以异步执行。对于 Windows 应用程序,耗时的任务可以在后台执行,而使应用程序窗口和控件保持响应。对于服务器应用程序,多线程处理提供了用不同线程处理每个传入请求的能力。否则,在完全满足前一个请求之前,将无法处理每个新请求。

然而,线程的异步特性意味着必须协调对资源(如文件句柄、网络连接和内存)的访问。否则,两个或更多的线程可能在同一时间访问相同的资源,而每个线程都不知道其他线程的操作。结果将产生不可预知的数据损坏。

对于整数数据类型的简单操作,可以用 Interlocked 类的成员来实现线程同步。对于其他所有数据类型和非线程安全的资源,只有使用本主题中的结构才能安全地执行多线程处理。

有关多线程编程的背景信息

1.

lock 关键字

lock 关键字可以用来确保代码块完成运行,而不会被其他线程中断。这是通过在代码块运行期间为给定对象获取互斥锁来实现的。

lock 语句以关键字 lock 开头,它有一个作为参数的对象,在该参数的后面还有一个一次只能由一个线程执行的代码块。例如:

隐藏行号 复制代码 这是一段程序代码。
  1.     private System.Object lockThis = new System.Object();
    
  2.     public void Function()
    
  3.     {
    
  4.         lock (lockThis)
    
  5.         {
    
  6.             // Access thread-sensitive resources.
    
  7.         }
    
  8.     }
    
  9. }
    

 

2AutoResetEvent和ManualResetEvent

 

我顶 字号:

在.Net多线程编程中,AutoResetEvent和ManualResetEvent这两个类经常用到, 他们的用法很类似,但也有区别。
Set方法将信号置为发送状态,Reset方法将信号置为不发送状态,WaitOne等待信号的发送。

可以通过构造函数的参数值来决定其初始状态,若为true则非阻塞状态,为false为阻塞状态。

如果某个线程调用WaitOne方法,则当信号处于发送状态时,该线程会得到信号, 继续向下执行。


其区别就在调用后,AutoResetEvent.WaitOne()每次只允许一个线程进入,当某个线程得到信号后,AutoResetEvent会自动又将信号置为不发送状态,则其他调用WaitOne的线程只有继续等待.也就是说,AutoResetEvent一次只唤醒一个线程;

而ManualResetEvent则可以唤醒多个线程,因为当某个线程调用了 ManualResetEvent.Set()方法后,其他调用WaitOne的线程获得信号得以继续执行,而ManualResetEvent不会自动将信号置为不发送。

也就是说,除非手工调用了ManualResetEvent.Reset()方法,则 ManualResetEvent将一直保持有信号状态,ManualResetEvent也就可以同时唤醒多个线程继续执行。



一个简单的例子

//先声明两个同步对象

        ManualResetEvent mre = new ManualResetEvent(false);
        AutoResetEvent are = new AutoResetEvent(false);


//启动两个线程


            System.Threading.Thread t1 = new Thread(T1);
            t1.IsBackground = true;
            t1.Start();

            System.Threading.Thread t2 = new Thread(T2);
            t2.IsBackground = true;
            t2.Start();

//线程调用的方法

 public void T1()
        {
              mre.WaitOne();//注意这个地方
            Console.WriteLine("t1......");
        }

        public void T2()
        {
            mre.WaitOne();
            Console.WriteLine("t2......");
        }

然后再设置一个按钮方法用来发送信号,注意红字的地方

第一我们使用ManualResetEvent对象来调用waitOne。然后执行按钮方法 mre.set();这个时候会输出结果

t1......

t2......

//表明ManualResetEvent发一次信息就可以让所有等待的线程都结束等待,开始运行

第二种情况。我们使用AutoResetEvent来调用waitOne。然后执行按钮方法are.set();这个时候会输出结果

t1......(//或者是t2......,谁先执行并没有一定的关系)

会发现点击一次按钮的are.set()只会结束一个线程的等待,另外一个线程仍然处于等待状态,

只有点击两次按钮执行are.set()方法才能让两个等待线程都结束等待

 

  AutoResetEvent:1.Set 一次只唤醒一个程序。调用了WaitOne的程序将开始运行。

                         

  ManualResetEvent:1。Set:所有调用WaitOne()的程序都将收到开始执行信号,变为非终止状态而执行。

                               2. ReSet:使WaitOne生效。

 

示例 2

 

代码
using System;
using System.Threading;

class CalculateTest
{
    
static void Main()
    {
        Calculate calc 
= new Calculate();
        Console.WriteLine(
"Result = {0}."
            calc.Result(
234).ToString());
        Console.WriteLine(
"Result = {0}."
            calc.Result(
55).ToString());
    }
}

class Calculate
{
    
double baseNumber, firstTerm, secondTerm, thirdTerm;
    AutoResetEvent[] autoEvents;
    ManualResetEvent manualEvent;

    
// Generate random numbers to simulate the actual calculations.
    Random randomGenerator;

    
public Calculate()
    {
        autoEvents 
= new AutoResetEvent[]
        {
            
new AutoResetEvent(false),
            
new AutoResetEvent(false),
            
new AutoResetEvent(false)
        };

        manualEvent 
= new ManualResetEvent(false);
    }

    
void CalculateBase(object stateInfo)
    {
        baseNumber 
= randomGenerator.NextDouble();

        
// Signal that baseNumber is ready.
        //其他调用了WaitOne的三个函数将同时收到执行信号,并开始执行。
        manualEvent.Set();
    }

    
// The following CalculateX methods all perform the same
    
// series of steps as commented in CalculateFirstTerm.

    
void CalculateFirstTerm(object stateInfo)
    {
        
// Perform a precalculation.
        double preCalc = randomGenerator.NextDouble();

        
// Wait for baseNumber to be calculated.
        manualEvent.WaitOne();

        
// Calculate the first term from preCalc and baseNumber.
        firstTerm = preCalc * baseNumber * 
            randomGenerator.NextDouble();

        
// Signal that the calculation is finished.
        autoEvents[0].Set();
    }

    
void CalculateSecondTerm(object stateInfo)
    {
        
double preCalc = randomGenerator.NextDouble();
        manualEvent.WaitOne();
        secondTerm 
= preCalc * baseNumber * 
            randomGenerator.NextDouble();
        autoEvents[
1].Set();
    }

    
void CalculateThirdTerm(object stateInfo)
    {
        
double preCalc = randomGenerator.NextDouble();
        manualEvent.WaitOne();
        thirdTerm 
= preCalc * baseNumber * 
            randomGenerator.NextDouble();
        autoEvents[
2].Set();
    }

    
public double Result(int seed)
    {
        randomGenerator 
= new Random(seed);

        
// Simultaneously calculate the terms.
        ThreadPool.QueueUserWorkItem(
            
new WaitCallback(CalculateBase));
        ThreadPool.QueueUserWorkItem(
            
new WaitCallback(CalculateFirstTerm));
        ThreadPool.QueueUserWorkItem(
            
new WaitCallback(CalculateSecondTerm));
        ThreadPool.QueueUserWorkItem(
            
new WaitCallback(CalculateThirdTerm));

        
// Wait for all of the terms to be calculated.
        //只有数组里autoEvents所有元素都调用了Set后才能继续往后执行。
        WaitHandle.WaitAll(autoEvents);

        
// Reset the wait handle for the next calculation.
        //否则下次执行时WaitOne无效。 
        manualEvent.Reset();

        
return firstTerm + secondTerm + thirdTerm;
    }
}

 

 三。线程池(ThreadPool )

 1。后台执行。

2。添加一个线程到线程池(具体什么时候执行不晓得)

     QueueUserWorkItem

 

代码
using System;
using System.Threading;
public class Example {
    
public static void Main() {
        
// Queue the task.(插入一个线程)
        ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc));

        Console.WriteLine(
"Main thread does some work, then sleeps.");
        
// If you comment out the Sleep, the main thread exits before
        
// the thread pool task runs.  The thread pool uses background
        
// threads, which do not keep the application running.  (This
        
// is a simple example of a race condition.)
        Thread.Sleep(1000);

        Console.WriteLine(
"Main thread exits.");
    }

    
// This thread procedure performs the task.
    static void ThreadProc(Object stateInfo) {
        
// No state object was passed to QueueUserWorkItem, so 
        
// stateInfo is null.
        Console.WriteLine("Hello from the thread pool.");
    }
}

 

   

 

                             

posted @ 2010-05-12 09:03  SouthAurora  Views(302)  Comments(1Edit  收藏  举报