打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
多线程1

C#多线程之Thread

.NET将关于多线程的功能定义在System.Threading名称空间中,因此,如果您的程序要使用多线程,必须引用此命名空间(using System.Threading)。
我们知道,在.NET中使用多线程有两种方式:
1,使用Thread创建一个新的线程。
2,使用ThreadPool。

首先我们先说说和Thread有关的几个概念。
1,创建线程和启动线程,如果代码可实现

Thread newThread = new Thread(new ThreadStart(Work.DoWork));
newThread.Start();
或者
Thread newThread = new Thread(new ParameterizedThreadStart(Work.DoWork));
newThread.Start(
42);

ParameterizedThreadStart 此委托在 .NET Framework 2.0 版中是新增的
在创建托管的线程时,在该线程上执行的方法将通过一个传递给 Thread 构造函数的 ThreadStart 委托或 ParameterizedThreadStart 委托来表示。
在调用 System.Threading.Thread.Start 方法之前,该线程不会开始执行。执行将从 ThreadStart 或 ParameterizedThreadStart 委托表示的方法的第一行开始。
ParameterizedThreadStart 委托和 Thread.Start(Object) 方法重载使得将数据传递给线程过程变得简单,但由于可以将任何对象传递给 Thread.Start(Object),因此这种方法并不是类型安全的。
将数据传递给线程过程的一个更可靠的方法是将线程过程和数据字段都放入辅助对象。

下面的代码示例演示通过静态方法和实例方法创建和使用 ParameterizedThreadStart 委托的语法,ThreadStart委托的使用方法和ParameterizedThreadStart一样,
唯一的区别在ThreadStart封装的方法不需要参数。(实例来自MSDN)

using System;
using System.Threading;

public class Work
{
    
public static void Main()
    
{
        
// To start a thread using a shared thread procedure, use
        
// the class name and method name when you create the 
        
// ParameterizedThreadStart delegate.
        
//
        Thread newThread = new Thread(
            
new ParameterizedThreadStart(Work.DoWork));
        
        
// Use the overload of the Start method that has a
        
// parameter of type Object. You can create an object that
        
// contains several pieces of data, or you can pass any 
        
// reference type or value type. The following code passes
        
// the integer value 42.
        
//
        newThread.Start(42);

        
// To start a thread using an instance method for the thread 
        
// procedure, use the instance variable and method name when 
        
// you create the ParameterizedThreadStart delegate.
        
//
        Work w = new Work();
        newThread 
= new Thread(
            
new ParameterizedThreadStart(w.DoMoreWork));
        
        
// Pass an object containing data for the thread.
        
//
        newThread.Start("The answer.");
    }

 
    
public static void DoWork(object data)
    
{
        Console.WriteLine(
"Static thread procedure. Data='{0}'",
            data);
    }


    
public void DoMoreWork(object data)
    
{
        Console.WriteLine(
"Instance thread procedure. Data='{0}'",
            data);
    }

}


/* This code example produces the following output (the order 
   of the lines might vary):

Static thread procedure. Data='42'
Instance thread procedure. Data='The answer'
*/

2,挂起线程
挂起线程分为两种,主动挂起和被动挂起。
主动挂起可表示为:
Thread.Sleep (Int32) 或 Thread.Sleep (TimeSpan) ,表示将当前调用线程挂起指定的时间。
被动挂起表示为:

Thread newThread = new Thread(new ThreadStart(Work.DoWork));
newThread.Start();
newThread.Join();

或者
Thread.CurrentThread.Join(50);

Join表示在继续执行标准的 COM 和 SendMessage 消息泵处理期间,阻止调用线程,直到某个线程终止为止。
在[STAThread]指示的Com线程模型中,我们应该使用Thread.CurrentThread.Join(50)这种方式。有一个与之有关的示例,
Thread.Sleep vs. Thread.CurrentThread.Join

3,终止线程
在调用 Abort 方法以销毁线程时,公共语言运行库将引发 ThreadAbortException。ThreadAbortException 是一种可捕获的特殊异常,但在 catch 块的结尾处它将自动被再次引发。
引发此异常时,运行库将在结束线程前执行所有 finally 块。由于线程可以在 finally 块中执行未绑定计算,或调用 Thread.ResetAbort 来取消中止,所以不能保证线程将完全结束。
如果您希望一直等到被中止的线程结束,可以调用 Thread.Join 方法。Join 是一个模块化调用,它直到线程实际停止执行时才返回。
注意:在托管可执行文件中的所有前台线程已经结束后,当公共语言运行库 (CLR) 停止后台线程时,它不使用 System.Threading.Thread.Abort。
因此,无法使用 ThreadAbortException 来检测 CLR 何时终止后台线程。
下面的示例说明如何中止线程。接收 ThreadAbortException 的线程使用 ResetAbort 方法取消中止请求并继续执行。(示例来自MSDN)

using System;
using System.Threading;
using System.Security.Permissions;

public class ThreadWork {
    
public static void DoWork() {
        
try {
            
for(int i=0; i<100; i++{
                Console.WriteLine(
"Thread - working."); 
                Thread.Sleep(
100);
            }

        }

        
catch(ThreadAbortException e) {
            Console.WriteLine(
"Thread - caught ThreadAbortException - resetting.");
            Console.WriteLine(
"Exception message: {0}", e.Message);
            Thread.ResetAbort();
        }

        Console.WriteLine(
"Thread - still alive and working."); 
        Thread.Sleep(
1000);
        Console.WriteLine(
"Thread - finished working.");
    }

}


class ThreadAbortTest {
    
public static void Main() {
        ThreadStart myThreadDelegate 
= new ThreadStart(ThreadWork.DoWork);
        Thread myThread 
= new Thread(myThreadDelegate);
        myThread.Start();
        Thread.Sleep(
100);
        Console.WriteLine(
"Main - aborting my thread.");
        myThread.Abort();
        myThread.Join();
        Console.WriteLine(
"Main ending."); 
    }

}

这段代码产生以下输出:
 Thread - working.
 Main - aborting my thread.
 Thread - caught ThreadAbortException - resetting.
 Exception message: Thread was being aborted.
 Thread - still alive and working.
 Thread - finished working.
 Main ending.

最后还有一点需要说明的是,在.NET Framework 2.0后,Thread对象的Suspend和Resume方法已被摈弃。
原因在于使用 Suspend 和 Resume 方法来同步线程的活动。您无法知道挂起线程时它正在执行什么代码。
如果您在安全权限评估期间挂起持有锁的线程,则 AppDomain 中的其他线程可能被阻止。
如果您在线程正在执行类构造函数时挂起它,则 AppDomain 中试图使用该类的其他线程将被阻止。很容易发生死锁。
 

调用A a=new A()
请问输出是什么?为什么?

 
class A{static A(){Stopwatch sw = new Stopwatch();sw.Start();XTrace.WriteLine("A1");Thread.Sleep(3000);//B b = new B();XTrace.WriteLine("AA");//ThreadPool.QueueUserWorkItem(delegate { XTrace.WriteLine("BB"); B b = new B(); });Thread thread = new Thread(new ParameterizedThreadStart(delegate { XTrace.WriteLine("BB"); B b = new B(); }));thread.Start();Thread.Sleep(3000);sw.Stop();XTrace.WriteLine("A2 " + sw.Elapsed);}public A() { XTrace.WriteLine("new A"); }}class B{static B(){Stopwatch sw = new Stopwatch();sw.Start();XTrace.WriteLine("B1");Thread.Sleep(3000);A a = new A();Thread.Sleep(3000);sw.Stop();XTrace.WriteLine("B2 " + sw.Elapsed);}public B() { XTrace.WriteLine("new B"); }}

 

关于静态构造函数函数的基本常识就不多说,园子里随处可见!

这个问题让群里的高手纠结了一整天,那个线程为什么不动?(线程等到A静态构造函数执行完毕后才执行)

 

傍晚时分,有人忍不住发信问微软:

Z_(164734xxx) 19:19:25
A static constructor is never called more than once, and it must be finished before any other thread can create an instance of the class or use a static member of the class. Therefore, the thread you try starting cannot start before A's static constructor ends.

 

网上很多资料说到静态构造函数,但是很少提到与线程相关的,这个例子实际上是想测试一下静态构造函数的多线程冲突。

 

其实,这个问题源自于XCode v7.3中一个隐秘的BUG。

实体类A的静态构造函数中可能会开一个线程去执行方法B,然后静态构造函数接着执行后续方法C,问题就在于B和C都会争夺同一个锁,如果B拿到这个锁,它会创建一个A的实例,但是因为A的静态构造函数正常执行C,C又等待B释放这个锁,从而形成了死锁,所有用到类型A的线程都会挂起。

因为B和C的执行速度不一样,要是C先拿到资源,就不会出现死锁,所以这个问题解决起来特别的麻烦!

 

XCode v7.3的这个BUG表明,那个线程应该是可以同步执行的,但是为什么测试项目里面线程就是不动呢?(先看看大家讨论,后面再公布答案)

 

附上XCode中出错的部分

/// <summary>/// 数据实体类基类。所有数据实体类都必须继承该类。/// </summary>[Serializable]public partial class Entity<TEntity> : EntityBase where TEntity : Entity<TEntity>, new(){#region 构造函数/// <summary>/// 静态构造/// </summary>static Entity(){// 1,可以初始化该实体类型的操作工厂// 2,CreateOperate将会实例化一个TEntity对象,从而引发TEntity的静态构造函数,// 避免实际应用中,直接调用Entity的静态方法时,没有引发TEntity的静态构造函数。TEntity entity = new TEntity();EntityFactory.CreateOperate(Meta.ThisType, entity);}

 

TEntity就是实体类,它本身也有静态构造函数,并且它的静态构造函数里面会开一个线程去调用EntityFactory.CreateOperate(Type type),该方法会取得一个字典的锁,然后通过Activator.CreateInstance(type)创建类型type的实例,加入字典,也就是实体类本身的实例。

EntityFactory.CreateOperate(Type type, IEntityOperate entity)跟上面的EntityFactory.CreateOperate(Type type)共同点再也它也要那这个字典的锁,不同的在于它只是把entity加入字典。

结果就是:如果两个参数这个先执行,就没有问题,如果一个参数那个先执行,大家一起死!

 

答案:

上面微软的答复邮件说得很清楚,静态构造函数只会被调用一次,并且在它执行完成之前,任何其它线程都不能创建这个类的实例或使用这个类的静态成员!

这里面包含几层一次:

1,静态构造函数只会被调用一次,并且在所有对该类的访问之前。这一点我确信99.99%的人都知道。

2,“其它线程”。也就是说,只是其它线程不能创建实例和调用静态成员而已,当前线程仍然是可以的。

3,“创建实例或使用静态成员”。那么实例成员呢?当然不可能了,因为实例都无法创建,如何使用实例成员?

4,也是最隐秘的地方。测试代码中,在A的静态构造函数里面使用了匿名函数,而编译器会把它编译成为A的一个静态方法,因此,它就成了A的静态成员了,所以……

 

实际上,我们没注意到的地方是第四点,太粗心了!

不过,可能清楚第二点的人不到10%吧。

C#多线程学习

任何程序在执行时,至少有一个主线程。在.net framework class library中,所有与多线程机制应用相关的类都是放在System.Threading命名空间中的。如果你想在你的应用程序中使用多线程,就必须包含这个类。

Thread类有几个至关重要的方法,描述如下:

Start():启动线程;

Sleep(int):静态方法,暂停当前线程指定的毫秒数;

Abort():通常使用该方法来终止一个线程;

Suspend():该方法并不终止未完成的线程,它仅仅挂起线程,以后还可恢复;

Resume():恢复被Suspend()方法挂起的线程的执行。

 

一个直观印象的线程示例:

using System;
using System.Threading;

namespace ThreadTest
{
  
class RunIt
  {
    [STAThread]
    
static void Main(string[] args)
    {
      Thread.CurrentThread.Name
="System Thread";//给当前线程起名为"System Thread"
Console.WriteLine(Thread.CurrentThread.Name+"'Status:"+Thread.CurrentThread.ThreadState);
      Console.ReadLine();
    }
  }
}

输出如下:

System Thread's Status:Running

 

在C#中,线程入口是通过ThreadStart代理(delegate)来提供的,你可以把ThreadStart理解为一个函数指针,指向线程要执行的函数,当调用Thread.Start()方法后,线程就开始执行ThreadStart所代表或者说指向的函数。

1.所有线程都是依附于Main()函数所在的线程的,Main()函数是C#程序的入口,起始线程可以称之为主线程。如果所有的前台线程都停止了,那么主线程可以终止,而所有的后台线程都将无条件终止。所有的线程虽然在微观上是串行执行的,但是在宏观上你完全可以认为它们在并行执行。

 

2.ThreadState 属性的取值如下,这个属性代表了线程运行时状态,在不同的情况下有不同的值,我们有时候可以通过对该值的判断来设计程序流程。:

Aborted:线程已停止;

AbortRequested:线程的Thread.Abort()方法已被调用,但是线程还未停止;

Background:线程在后台执行,与属性Thread.IsBackground有关;

Running:线程正在正常运行;

Stopped:线程已经被停止;

StopRequested:线程正在被要求停止;

Suspended:线程已经被挂起(此状态下,可以通过调用Resume()方法重新运行);

SuspendRequested:线程正在要求被挂起,但是未来得及响应;

Unstarted:未调用Thread.Start()开始线程的运行;

WaitSleepJoin:线程因为调用了Wait(),Sleep()或Join()等方法处于封锁状态;

 

3.当线程之间争夺CPU时间时,CPU 是按照线程的优先级给予服务的。在C#应用程序中,用户可以设定5个不同的优先级,由高到低分别是Highest,AboveNormal,Normal,BelowNormal,Lowest,在创建线程时如果不指定优先级,那么系统默认为ThreadPriority.Normal。

例:

using System;
using System.Threading;

namespace ThreadTest
{
public class Alpha
{
public void Beta()
{
while (true)
{
Console.WriteLine(
"Alpha.Beta is running in its own thread.");
}
}
};

public class Simple
{
public static int Main()
{
  Console.WriteLine(
"Thread Start/Stop/Join Sample");
Alpha oAlpha
= new Alpha();
   file:
//这里创建一个线程,使之执行Alpha类的Beta()方法
   Thread oThread = new Thread(new ThreadStart(oAlpha.Beta));
   oThread.Start();
  
while (!oThread.IsAlive)
   Thread.Sleep(
1);
   oThread.Abort();
   oThread.Join();
   Console.WriteLine();
   Console.WriteLine(
"Alpha.Beta has finished");
  
try
   {
     Console.WriteLine(
"Try to restart the Alpha.Beta thread");
     oThread.Start();
   }
  
catch (ThreadStateException)
   {
     Console.Write(
"ThreadStateException trying to restart Alpha.Beta. ");
     Console.WriteLine(
"Expected since aborted threads cannot be restarted.");
     Console.ReadLine();
   }
  
return 0;
   }
}
}

 

C#提供了一个关键字lock,它可以把一段代码定义为互斥段(critical section),互斥段在一个时刻内只允许一个线程进入执行,而其他线程必须等待。在C#中,关键字lock定义如下:

lock() statement_block

代表你希望跟踪的对象,通常是对象引用。

  • 如果你想保护一个类的实例,一般地,你可以使用this;
  • 如果你想保护一个静态变量(如互斥代码段在一个静态方法内部),一般使用类名就可以了。

而statement_block就是互斥段的代码,这段代码在一个时刻内只可能被一个线程执行。

示例如下:

using System;
using System.Threading;

namespace ThreadSimple
{
   
internal class Account
    {
       
int balance;
        Random r
= new Random();
       
       
internal Account(int initial)
        {
            balance
= initial;
        }
       
internal int Withdraw(int amount)
        {
           
if (balance < 0)
            {
               
//如果balance小于0则抛出异常
                throw new Exception("Negative Balance");
            }
           
//下面的代码保证在当前线程修改balance的值完成之前
           
//不会有其他线程也执行这段代码来修改balance的值
           
//因此,balance的值是不可能小于0 的
            lock (this)
            {
                Console.WriteLine(
"Current Thread:"+Thread.CurrentThread.Name);
               
//如果没有lock关键字的保护,那么可能在执行完if的条件判断之后
               
//另外一个线程却执行了balance=balance-amount修改了balance的值
               
//而这个修改对这个线程是不可见的,所以可能导致这时if的条件已经不成立了
               
//但是,这个线程却继续执行balance=balance-amount,所以导致balance可能小于0
                if (balance >= amount)
                {
                    Thread.Sleep(
5);
                    balance
= balance - amount;
                   
return amount;
                }
               
else
                {
                   
return 0; // transaction rejected
                  }
            }
        }
       
internal void DoTransactions()
        {
           
for (int i = 0; i < 100; i++)
            Withdraw(r.Next(
-50, 100));
        }
    }
   
internal class Test
    {
       
static internal Thread[] threads = new Thread[10];
       
public static void Main()
        {
            Account acc
= new Account (0);
           
for (int i = 0; i < 10; i++)
            {
                Thread t
= new Thread(new ThreadStart(acc.DoTransactions));
                threads[i]
= t;
            }
           
for (int i = 0; i < 10; i++)
                threads[i].Name
=i.ToString();
           
for (int i = 0; i < 10; i++)
                threads[i].Start();
            Console.ReadLine();
        }
    }
}

 

当多线程公用一个对象时,也会出现和公用代码类似的问题,这种问题就不应该使用lock关键字了,这里需要用到System.Threading中的一个类Monitor,我们可以称之为监视器,Monitor提供了使线程共享资源的方案。

Monitor类可以锁定一个对象,一个线程只有得到这把锁才可以对该对象进行操作。对象锁机制保证了在可能引起混乱的情况下一个时刻只有一个线程可以访问这个对象。 Monitor必须和一个具体的对象相关联,但是由于它是一个静态的类,所以不能使用它来定义对象,而且它的所有方法都是静态的,不能使用对象来引用。下面代码说明了使用Monitor锁定一个对象的情形:

......

Queue oQueue=new Queue();

......

Monitor.Enter(oQueue);

......//现在oQueue对象只能被当前线程操纵了

Monitor.Exit(oQueue);//释放锁

 

在多线程的程序中,经常会出现两种情况:

一种情况: 应用程序中,线程把大部分的时间花费在等待状态,等待某个事件发生,然后才能给予响应

这一般使用ThreadPool(线程池)来解决;

另一种情况:线程平时都处于休眠状态,只是周期性地被唤醒

这一般使用Timer(定时器)来解决;

ThreadPool类提供一个由系统维护的线程池(可以看作一个线程的容器),

将线程安放在线程池里,需使用ThreadPool.QueueUserWorkItem()方法,该方法的原型如下:

//将一个线程放进线程池,该线程的Start()方法将调用WaitCallback代理对象代表的函数

public static bool QueueUserWorkItem(WaitCallback);

//重载的方法如下,参数object将传递给WaitCallback所代表的方法

public static bool QueueUserWorkItem(WaitCallback, object);

在这里你无需自己建立线程,只需把你要做的工作写成函数,然后作为参数传递给ThreadPool.QueueUserWorkItem()方法就行了,传递的方法就是依靠WaitCallback代理对象,而线程的建立、管理、运行等工作都是由系统自动完成的,你无须考虑那些复杂的细节问题。

首先程序创建了一个ManualResetEvent对象,该对象就像一个信号灯,可以利用它的信号来通知其它线程。

在初始化以后,该对象将保持原来的状态不变,直到它的Reset()或者Set()方法被调用:

Reset()方法:将其设置为无信号状态;

Set()方法:将其设置为有信号状态。

WaitOne()方法:使当前线程挂起,直到ManualResetEvent对象处于有信号状态,此时该线程将被激活.然后,程序将向线程池中添加工作项,这些以函数形式提供的工作项被系统用来初始化自动建立的线程。当所有的线程都运行完了以后,ManualResetEvent.Set()方法被调用,因为调用了ManualResetEvent.WaitOne()方法而处在等待状态的主线程将接收到这个信号,于是它接着往下执行,完成后边的工作。

using System;
using System.Collections;
using System.Threading;

namespace ThreadExample
{
   
//这是用来保存信息的数据结构,将作为参数被传递
    public class SomeState
    {
     
public int Cookie;
     
public SomeState(int iCookie)
      {
        Cookie
= iCookie;
      }
    }

   
public class Alpha
    {
  
public Hashtable HashCount;
  
public ManualResetEvent eventX;
  
public static int iCount = 0;
  
public static int iMaxCount = 0;
  
       
public Alpha(int MaxCount)
  {
         HashCount
= new Hashtable(MaxCount);
         iMaxCount
= MaxCount;
  }

  
//线程池里的线程将调用Beta()方法
  public void Beta(Object state)
  {
     
//输出当前线程的hash编码值和Cookie的值
         Console.WriteLine(" {0} {1} :", Thread.CurrentThread.GetHashCode(),((SomeState)state).Cookie);
      Console.WriteLine(
"HashCount.Count=={0}, Thread.CurrentThread.GetHashCode()=={1}", HashCount.Count, Thread.CurrentThread.GetHashCode());
     
lock (HashCount)
      {
       
//如果当前的Hash表中没有当前线程的Hash值,则添加之
        if (!HashCount.ContainsKey(Thread.CurrentThread.GetHashCode()))
             HashCount.Add (Thread.CurrentThread.GetHashCode(),
0);
         HashCount[Thread.CurrentThread.GetHashCode()]
=
            ((
int)HashCount[Thread.CurrentThread.GetHashCode()])+1;
      }
         
int iX = 2000;
          Thread.Sleep(iX);
         
//Interlocked.Increment()操作是一个原子操作,具体请看下面说明
          Interlocked.Increment(ref iCount);

         
if (iCount == iMaxCount)
          {
          Console.WriteLine();
        Console.WriteLine(
"Setting eventX ");
        eventX.Set();
        }
    }
  }

       
public class SimplePool
        {
           
public static int Main(string[] args)
            {
                Console.WriteLine(
"Thread Pool Sample:");
               
bool W2K = false;
               
int MaxCount = 10;//允许线程池中运行最多10个线程
               
//新建ManualResetEvent对象并且初始化为无信号状态
                ManualResetEvent eventX = new ManualResetEvent(false);
                Console.WriteLine(
"Queuing {0} items to Thread Pool", MaxCount);
                Alpha oAlpha
= new Alpha(MaxCount);
               
//创建工作项
               
//注意初始化oAlpha对象的eventX属性
                oAlpha.eventX = eventX;
                Console.WriteLine(
"Queue to Thread Pool 0");
               
try
                {
                   
//将工作项装入线程池
                   
//这里要用到Windows 2000以上版本才有的API,所以可能出现NotSupportException异常
                    ThreadPool.QueueUserWorkItem(new WaitCallback(oAlpha.Beta), new SomeState(0));
                    W2K
= true;
                }
               
catch (NotSupportedException)
                {
                    Console.WriteLine(
"These API's may fail when called on a non-Windows 2000 system.");
                    W2K
= false;
                }
               
if (W2K)//如果当前系统支持ThreadPool的方法.
                {
                   
for (int iItem=1;iItem < MaxCount;iItem++)
                    {
                       
//插入队列元素
                        Console.WriteLine("Queue to Thread Pool {0}", iItem);
                        ThreadPool.QueueUserWorkItem(
new WaitCallback(oAlpha.Beta), new SomeState(iItem));
                    }
                    Console.WriteLine(
"Waiting for Thread Pool to drain");
                   
//等待事件的完成,即线程调用ManualResetEvent.Set()方法
                    eventX.WaitOne(Timeout.Infinite,true);
                   
//WaitOne()方法使调用它的线程等待直到eventX.Set()方法被调用
                    Console.WriteLine("Thread Pool has been drained (Event fired)");
                    Console.WriteLine();
                    Console.WriteLine(
"Load across threads");
                   
foreach(object o in oAlpha.HashCount.Keys)
                        Console.WriteLine(
"{0} {1}", o, oAlpha.HashCount[o]);
                }
                Console.ReadLine();
               
return 0;
            }
        }
    }
}

 

Timer类:设置一个定时器,定时执行用户指定的函数。

定时器启动后,系统将自动建立一个新的线程,执行用户指定的函数。

初始化一个Timer对象:

Timer timer = new Timer(timerDelegate, s,1000, 1000);

// 第一个参数:指定了TimerCallback 委托,表示要执行的方法;

// 第二个参数:一个包含回调方法要使用的信息的对象,或者为空引用;

// 第三个参数:延迟时间——计时开始的时刻距现在的时间,单位是毫秒,指定为“0”表示立即启动计时器;

// 第四个参数:定时器的时间间隔——计时开始以后,每隔这么长的一段时间,TimerCallback所代表的方法将被调用一次,单位也是毫秒。指定 Timeout.Infinite 可以禁用定期终止。

Timer.Change()方法:修改定时器的设置。(这是一个参数类型重载的方法)

使用示例: timer.Change(1000,2000);

 

如何控制好多个线程相互之间的联系,不产生冲突和重复,这需要用到互斥对象,即:System.Threading 命名空间中的 Mutex 类。线程使用Mutex.WaitOne()方法等待Mutex对象被释放,如果它等待的Mutex对象被释放了,它就自动拥有这个对象,直到它调用Mutex.ReleaseMutex()方法释放这个对象,而在此期间,其他想要获取这个Mutex对象的线程都只有等待。

其中还用到AutoResetEvent类的对象,可以把它理解为一个信号灯。这里用它的有信号状态来表示一个线程的结束。

// AutoResetEvent.Set()方法设置它为有信号状态

// AutoResetEvent.Reset()方法设置它为无信号状态

using System;
using System.Threading;

namespace ThreadExample
{
   
public class MutexSample
    {
     
static Mutex gM1;
     
static Mutex gM2;
     
const int ITERS = 100;
     
static AutoResetEvent Event1 = new AutoResetEvent(false);
     
static AutoResetEvent Event2 = new AutoResetEvent(false);
     
static AutoResetEvent Event3 = new AutoResetEvent(false);
     
static AutoResetEvent Event4 = new AutoResetEvent(false);

     
public static void Main(String[] args)
      {
            Console.WriteLine(
"Mutex Sample ");
           
//创建一个Mutex对象,并且命名为MyMutex
            gM1 = new Mutex(true,"MyMutex");
           
//创建一个未命名的Mutex 对象.
            gM2 = new Mutex(true);
            Console.WriteLine(
" - Main Owns gM1 and gM2");

            AutoResetEvent[] evs
= new AutoResetEvent[4];
            evs[
0] = Event1; //为后面的线程t1,t2,t3,t4定义AutoResetEvent对象
            evs[1] = Event2;
            evs[
2] = Event3;
            evs[
3] = Event4;

            MutexSample tm
= new MutexSample( );
            Thread t1
= new Thread(new ThreadStart(tm.t1Start));
            Thread t2
= new Thread(new ThreadStart(tm.t2Start));
            Thread t3
= new Thread(new ThreadStart(tm.t3Start));
            Thread t4
= new Thread(new ThreadStart(tm.t4Start));
            t1.Start( );
// 使用Mutex.WaitAll()方法等待一个Mutex数组中的对象全部被释放
            t2.Start( );// 使用Mutex.WaitOne()方法等待gM1的释放
            t3.Start( );// 使用Mutex.WaitAny()方法等待一个Mutex数组中任意一个对象被释放
            t4.Start( );// 使用Mutex.WaitOne()方法等待gM2的释放

            Thread.Sleep(
2000);
            Console.WriteLine(
" - Main releases gM1");
            gM1.ReleaseMutex( );
//线程t2,t3结束条件满足

            Thread.Sleep(
1000);
            Console.WriteLine(
" - Main releases gM2");
            gM2.ReleaseMutex( );
//线程t1,t4结束条件满足

           
//等待所有四个线程结束
            WaitHandle.WaitAll(evs);
            Console.WriteLine(
" Mutex Sample");
            Console.ReadLine();
      }

     
public void t1Start( )
      {
            Console.WriteLine(
"t1Start started, Mutex.WaitAll(Mutex[])");
            Mutex[] gMs
= new Mutex[2];
            gMs[
0] = gM1;//创建一个Mutex数组作为Mutex.WaitAll()方法的参数
            gMs[1] = gM2;
            Mutex.WaitAll(gMs);
//等待gM1和gM2都被释放
            Thread.Sleep(2000);
            Console.WriteLine(
"t1Start finished, Mutex.WaitAll(Mutex[]) satisfied");
            Event1.Set( );
//线程结束,将Event1设置为有信号状态
      }
     
public void t2Start( )
      {
            Console.WriteLine(
"t2Start started, gM1.WaitOne( )");
            gM1.WaitOne( );
//等待gM1的释放
            Console.WriteLine("t2Start finished, gM1.WaitOne( ) satisfied");
            Event2.Set( );
//线程结束,将Event2设置为有信号状态
      }
     
public void t3Start( )
      {
            Console.WriteLine(
"t3Start started, Mutex.WaitAny(Mutex[])");
            Mutex[] gMs
= new Mutex[2];
            gMs[
0] = gM1;//创建一个Mutex数组作为Mutex.WaitAny()方法的参数
            gMs[1] = gM2;
            Mutex.WaitAny(gMs);
//等待数组中任意一个Mutex对象被释放
            Console.WriteLine("t3Start finished, Mutex.WaitAny(Mutex[])");
            Event3.Set( );
//线程结束,将Event3设置为有信号状态
      }
     
public void t4Start( )
      {
            Console.WriteLine(
"t4Start started, gM2.WaitOne( )");
            gM2.WaitOne( );
//等待gM2被释放
            Console.WriteLine("t4Start finished, gM2.WaitOne( )");
            Event4.Set( );
//线程结束,将Event4设置为有信号状态
      }
    }
}
 

.NET 多线程异常处理

Posted on 2010-04-11 17:10 秋江 阅读(1905) 评论(12) 编辑 收藏

多线程应用,在实际的项目或产品开发中,原则上来说,应该尽量避免(这是我一家之言,因为我不是一个一心可多用的人

)。但是在强调用户体验的要求下或开发平台的限制下(如 Silverlight Socket 通讯),我们不得不用多线程。

多线程环境

在我们的产品 SE 中,出现多线程的地方主要有两大类,一类是通过 ThreadPool 或 new Thread 主动发起多线程,另一类是 Socket 通讯回调。

多线程异常捕获

对于一般的异常处理来说,我们只要简单的将可能出错的语句包含在 try/catch 语句中即可。我也曾经简单的将该方法运用于多线程的异常捕获,结果并非如此,代码如下:

public static void Main(){try{new Thread (Go).Start();}catch (Exception ex){// 永远执行不到这儿!Console.WriteLine ("Exception!");}}private static void Go(){throw null;}

正确的做法应该是在新线程入口方法 Go 中捕获异常:

public static void Main(){new Thread (Go).Start();}private static void Go(){try{...throw null; // 该异常将会被捕获...}catch (Exception ex){// 异常日志记录,或者通知其他线程出现异常了...}}

以上的正确做法来自 Threading in C# 中的小节 Exception Handling,该文涉及到 .NET 多线程的方方面面,是我看到最全最好的文章。

正确捕获多线程异常的方法找到了,接下来我们自然会想:是不是每个线程入口方法都得这么做?

且看 Threading in C# 中的小节 Exception Handling 的描述:从 .NET 2.0 开始,任何一个线程上未处理的异常都会导致整个应用程序关闭。因此,在每个线程入口方法中都必须要使用 try/catch 语句,至少在产品应用程序中必须如此,以免应用程序因为我们未预料到的代码而关闭整个应用程序。

如果仅仅记下异常信息而不在乎应用程序异常关闭,那么有两个方法可以做到:

  1、对于 Windows Form 程序来说,有一个全局异常处理事件:Application.ThreadException;

  2、对于所有 .NET 程序来说,还有一个更低级的全局异常处理事件:AppDomain.UnhandledException;

更高的要求

我们能简单的通过全局异常处理事件来记录错误日志;如果保证不中断应用程序,也可以在每个线程入口方法中捕获异常并记录异常日志。有没有办法做到:既能捕获异常且不中断应用程序,又能如全局异常处理事件那样简单捕获异常?

对于主动创建的新线程,至少可以做到这一点:

public static class ThreadExecutor{public static bool Execute(System.Threading.WaitCallback callback, object state){try{return System.Threading.ThreadPool.QueueUserWorkItem((data) =>{try{callback(data);}catch (exception ex){// log the exception}}, state);}catch (Exception e){// log the exception}return false;}}
感谢月照孤周的提醒!我已经修改了上面的代码,之前的是错误的。
 

一段比较经典的多线程学习代码

一段比较经典的多线程学习代码。

1、用到了多线程的同步问题。
2、用到了多线程的顺序问题。

如果有兴趣的请仔细阅读下面的代码。注意其中代码段的顺序,思考一下,这些代码的顺序能否互相调换,为什么?这应该对学习很有帮助的。为了演示,让所有的线程都Sleep了一段时间。

using System.Net;
using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Diagnostics;

namespace Webb.Study
{
    
class TestThread
    
{
        
static Mutex m_Mutex            = new Mutex();
        
static Thread[] m_testThreads    = new Thread[10];
        
static int m_threadIndex        = 0;

        
static void ThreadCallBack()
        
{
            TestThread.m_Mutex.WaitOne();
            
int m_index    = m_threadIndex;
            TestThread.m_Mutex.ReleaseMutex();
            Console.WriteLine(
"Thread {0} start.",m_index);
            
for(int i=0;i<=10;i++)
            

                TestThread.m_Mutex.WaitOne();     
                Console.WriteLine(
"Thread {0}: is running. {1}",m_index,i);
                TestThread.m_Mutex.ReleaseMutex();
                Thread.Sleep(
100);
            }

            Console.WriteLine(
"Thread {0} end.",m_index);
        }


        
public static void Main(String[] args)
        
{
            Console.WriteLine(
"Main thread start.");
            
for(int i=0;i<TestThread.m_testThreads.Length;i++)
            
{
                TestThread.m_threadIndex    
= i;
                TestThread.m_testThreads[i]    
= new Thread(new ThreadStart(ThreadCallBack));                
                TestThread.m_testThreads[i].Start();
                Thread.Sleep(
100);
            }

            
for(int i=0;i<TestThread.m_testThreads.Length;i++)
            
{
                TestThread.m_testThreads[i].Join();
            }

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

    }

}

1、主函数中这两句能否互换?为什么?
                TestThread.m_testThreads[i].Start();
                Thread.Sleep(100);

2、CallBack函数中这两句能否互换?为什么?会有什么不同的结果?
                TestThread.m_Mutex.ReleaseMutex();
                Thread.Sleep(100);

3、主函数能否写成这样?为什么?会有什么不同的结果?
        public static void Main(String[] args)
        
{
            Console.WriteLine(
"Main thread start.");
            
for(int i=0;i<TestThread.m_testThreads.Length;i++)
            
{
                TestThread.m_threadIndex    
= i;
                TestThread.m_testThreads[i]    
= new Thread(new ThreadStart(ThreadCallBack));                
                TestThread.m_testThreads[i].Start();
                TestThread.m_testThreads[i].Join();
                Thread.Sleep(
100);
            }

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

4、这几句的作用是什么?那么程序中还存在什么样的问题?应该做怎样的修改?
   TestThread.m_Mutex.WaitOne();
   int m_index = m_threadIndex;
   TestThread.m_Mutex.ReleaseMutex();

仅做学习讨论。
 
 

C#多线程点滴

< type="text/JavaScript"> < src="http://a.alimama.cn/inf.js" type="text/javascript">

 一、基本概念
     进程:当一个程序开始运行时,它就是一个进程,进程包括运行中的程序和程序所使用到的内存和系统资源。而一个进程又是由多个线程所组成的。
     线程:线程是程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针、程序计数器等),但代码区是共享的,即不同的线程可以执行同样的函数。
     多线程:多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。
     静态属性:这个类所有对象所公有的属性,不管你创建了多少个这个类的实例,但是类的静态属性在内存中只有一个。

二、多线程的优劣
     优点:可以提高CPU的利用率。在多线程程序中,一个线程必须等待的时候,CPU可以运行其它的线程而不是等待,这样就大大提高了程序的效率。
     缺点:线程也是程序,所以线程需要占用内存,线程越多占用内存也越多;
           多线程需要协调和管理,所以需要CPU时间跟踪线程;
           线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题;
           线程太多会导致控制太复杂,最终可能造成很多Bug;

三、控制线程的类和方法
     类:using System.Threading;  Thread类
     Thread类的方法:Start():启动线程;
                     Sleep(int):静态方法,暂停当前线程指定的毫秒数;
                     Abort():通常使用该方法来终止一个线程;
                     Suspend():该方法并不终止未完成的线程,它仅仅挂起线程,以后还可恢复;
                     Resume():恢复被Suspend()方法挂起的线程的执行。

四、如何操纵一个线程
     using System;
     using System.Threading;
     namespace ThreadTest
     {   
          public class Alpha
          {       
              public void Beta()   
              {       
                   while (true)           
                   {            
                       Console.WriteLine("Alpha.Beta is running in its own thread."); 
                   }    
              }  
          }
          public class Simple
          {  
              public static int Main()  
              {       
                   Console.WriteLine("Thread Start/Stop/Join Sample");
                   Alpha oAlpha = new Alpha();   
                   //这里创建一个线程,使之执行Alpha类的Beta()方法  
                   Thread oThread = new Thread(new ThreadStart(oAlpha.Beta));  
                   oThread.Start(); // 程序运行的是Alpha.Beta()方法
                   while (!oThread.IsAlive)    
                   Thread.Sleep(1);  //让主线程停1ms       
                   oThread.Abort();  //终止线程oThread      
                   oThread.Join();  //使主线程等待,直到oThread线程结束。可以指定一个int型的参数作为等待的最长时间 
                   Console.WriteLine();     
                   Console.WriteLine("Alpha.Beta has finished");   
                   try           
                   {          
                       Console.WriteLine("Try to restart the Alpha.Beta thread");    
                       oThread.Start();          
                   }        
                   catch (ThreadStateException)       
                   {       
                       Console.Write("ThreadStateException trying to restart Alpha.Beta. ");
                       Console.WriteLine("Expected since aborted threads cannot be restarted.");                           Console.ReadLine();  
                   }        
                   return 0;  
              }  
           }
     }

五、Thread.ThreadState 属性
     Aborted:线程已停止;
     AbortRequested:线程的Thread.Abort()方法已被调用,但是线程还未停止;
     Background:线程在后台执行,与属性Thread.IsBackground有关;不妨碍程序的终止
     Running:线程正在正常运行;
     Stopped:线程已经被停止;
     StopRequested:线程正在被要求停止;
     Suspended:线程已经被挂起(此状态下,可以通过调用Resume()方法重新运行);
     SuspendRequested:线程正在要求被挂起,但是未来得及响应;
     Unstarted:未调用Thread.Start()开始线程的运行;
     WaitSleepJoin:线程因为调用了Wait(),Sleep()或Join()等方法处于封锁状态;

六、线程的优先级
     由高到低分别是Highest,AboveNormal,Normal,BelowNormal,Lowest;系统默认为ThreadPriority.Normal
     指定优先级的代码:myThread.Priority=ThreadPriority.Lowest;

< type="text/JavaScript"> < src="http://a.alimama.cn/inf.js" type="text/javascript">
 
 
 
 
 
 
 
 
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
C# 温故而知新: 线程篇(四)
C#多线程编程(二)线程池与TPL
c++11 thread类简单使用
C#中的线程之Abort陷阱
关于多线程简单原理
C#线程同步的几种方法
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服