C#多线程编程

Posted blogpro

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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):所有方法都是执行一次原子读取或一次写入操作。
  2lock()语句:避免锁定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)跨进程间的同步
      通过设置同步对象的名称就可以实现系统级的同步,不同应用程序通过同步对象的名称识别不同同步对象。
View Code

带返回值的委托

技术图片
        delegate bool GCaller(string uid, string Tuid, string LY, CookieContainer Cookiecc);//cookie代理 
        public bool GetName(string uid,string Tuid, string LY, CookieContainer Cookiecc)    // 函数
        
            
        

使用方法
            GCaller Snd = new GCaller(GetName);
            IAsyncResult Sndresult = Snd.BeginInvoke(uid, Tuid, LY,Cookiecc, null, null);
            bool rsw = Snd.EndInvoke(Sndresult);//用于接收返回值 
View Code

控件显示值设置

技术图片
#region 委托方式线程更改进程条值

        public delegate void SetValue(ProgressBar C, int Value);

        public void DoSetValue(ProgressBar C, int Value)
        
            C.Value =  Value;
        
        public void SetValueWithDelegate(ProgressBar C, int Value)
        
            try
            
                SetValue PSetValue = new SetValue(DoSetValue);
                if (C.InvokeRequired)
                    C.Invoke(PSetValue, new Object[]  C, Value );
                else
                    C.Value = Value;
            
            catch  
        
        #endregion
        #region 委托方式线程更改控件文本
        //  SetTextWithDelegate(需要操作的控件, "需要修改的文本");  
        public delegate void SetText(Control C, String Text);

        public void DoSetText(Control C, String Text)
        
            C.Text = Text;
        
      public   void SetTextWithDelegate(Control C, String Text)
        
            try
            
                SetText PSetText = new SetText(DoSetText);
                if (C.InvokeRequired)
                    C.Invoke(PSetText, new Object[]  C, Text );
                else
                    C.Text = Text;
            
            catch  
        
        #endregion
View Code

C#2005 控件线程安全问题..委托

技术图片
public Thread t1;
        delegate void DoThingCallBack();
        private void button1_Click(object sender, EventArgs e)
        
              t1 = new Thread(new ThreadStart(caozuo1));
              t1.Start();
        
        private void caozuo1()
        
            if (webBrowser1.InvokeRequired)
            
                DoThingCallBack cb = new DoThingCallBack(caozuo1);
                this.Invoke(cb);
            
            else
            
                htmlElement guanli = webBrowser1.Document.All["Login"];
            
        

代碼有判斷webBrowser1.InvokeRequired,也就是當需要的時候才使用委托,而不是任何時候都使用。
View Code

C#发挥匿名委托的威力

技术图片
 这几天研究了一下Linq,C# 3.0中的“扩展方法”特性为IEnumerable<T>增加了诸如Where、Select等查询方法,这使得“语言集成查询”成为顺其自然的事情。而C#3.0中Linq的实现也是建立在C#2.0的匿名委托的特性之上。
   今天,我尝试在C#2.0中使用匿名委托模拟C#3.0中Where、Select等查询方法的实现。我将所有的查询方法作为静态方法在GenericHepler静态类中实现。
   之前,我们先定义泛型委托:
   public delegate TResult Func<T, TResult>(T source);
  这个委托在后面的实现中需要用到。

   作为基础,首先,我们需要实现ForSpecification方法,该方法的含义是:对集合中满足指定条件的元素执行指定方法调用。
 
         /// <summary>

        /// ForSpecification 对集合中满足predicate条件的元素执行action。如果没有条件,predicate传入null。

        /// </summary>       

        public static void ForSpecification<TSource>(IEnumerable<TSource> collection, Action<TSource> action, Predicate<TSource> predicate)

        

            if (predicate == null)

            

                foreach (TSource obj in collection)

                

                    action(obj);

                


                return;

            


            foreach (TSource obj in collection)

            

                if (predicate(obj))

                

                    action(obj);

                

            

        
   有了ForSpecification的实现,我们就可以在其基础上实现ForEach和ForFirstSpecification:
 
       #region ForEach

        /// <summary>

        /// ForEach  对集合中的每个元素执行action。

        /// </summary>        

        public static void ForEach<TSource>(IEnumerable<TSource> collection, Action<TSource> action)

        

            GenericHepler.ForSpecification<TSource>(collection, action, null);

        

        #endregion


        #region ForFirstSpecification

        /// <summary>

        /// ForSpecification 对集合中第一个满足predicate条件的元素执行action。如果没有条件,predicate传入null。

        /// </summary>       

        public static void ForFirstSpecification<TSource>(IEnumerable<TSource> collection, Action<TSource> action, Predicate<TSource> predicate)

        

            if (predicate == null)

            

                foreach (TSource obj in collection)

                

                    action(obj);

                    break;

                

            

            else

            

                foreach (TSource obj in collection)

                

                    if (predicate(obj))

                    

                        action(obj);

                        break;

                    

                

            

        

        #endregion
   有了ForSpecification,我们就可以实现查询方法Where:
       #region Where

        /// <summary>

        /// Where 从集合中选取符合条件的元素

        /// </summary>       

        public static IList<TSource> Where<TSource>(IEnumerable<TSource> source, Predicate<TSource> predicate)

        

            IList<TSource> list = new List<TSource>();

            GenericHepler.ForSpecification(source, delegate(TSource ele)  list.Add(ele);  , predicate);

            return list;

        

        #endregion
   对于C#3.0中的Select方法,其实现需要匿名类型的支持,而C#2.0中不支持匿名类型,所以,我用泛型来代替。我使用ConvertSpecification来模拟Select实现:
       #region ConvertSpecification

        /// <summary>

        /// ConvertSpecification 将source中的符合predicate条件元素转换为TResult类型

        /// </summary>       

        public static IList<TResult> ConvertSpecification<TSource, TResult>(IEnumerable<TSource> source, Func<TSource, TResult> converter, Predicate<TSource> predicate)

        

            IList<TResult> list = new List<TResult>();

            GenericHepler.ForSpecification<TSource>(source, delegate(TSource ele)  list.Add(converter(ele));  ,predicate);

            return list;

        

        #endregion
   converter委托用于从TSource类型对象构造TResult类型的对象。
   有了ConvertSpecification实现,我们就可以在其上继续实现ConvertAll和ConvertFirstSpecification:
       #region ConvertAll

        /// <summary>

        /// ConvertAll 将source中的每个元素转换为TResult类型

        /// </summary>       

        public static IList<TResult> ConvertAll<TSource, TResult>(IEnumerable<TSource> source, Func<TSource, TResult> converter)

        

            return GenericHepler.ConvertSpecification<TSource, TResult>(source, converter, null);

        

        #endregion


        #region ConvertFirstSpecification

        /// <summary>

        /// ConvertSpecification 将source中的符合predicate条件的第一个元素转换为TResult类型

        /// </summary>       

        public static TResult ConvertFirstSpecification<TSource, TResult>(IEnumerable<TSource> source, Func<TSource, TResult> converter, Predicate<TSource> predicate)

        

            TSource target = GenericHepler.GetFirstSpecification<TSource>(source, predicate);


            if (target == null)

            

                return default(TResult);

            


            return converter(target);

        

        #endregion       
   有了上面的基础,我们还可以实现ContainsSpecification方法:
       #region ContainsSpecification

        /// <summary>

        /// ContainsSpecification 集合中是否包含满足predicate条件的元素。

        /// </summary>       

        public static bool ContainsSpecification<TSource>(IEnumerable<TSource> source, Predicate<TSource> predicate, out TSource specification)

        

            specification = default(TSource);

            foreach (TSource element in source)

            

                if (predicate(element))

                

                    specification = element;

                    return true;

                

            


            return false;

        

        #endregion        


        #region ContainsSpecification

        /// <summary>

        /// ContainsSpecification 集合中是否包含满足predicate条件的元素。

        /// </summary>       

        public static bool ContainsSpecification<TSource>(IEnumerable<TSource> source, Predicate<TSource> predicate)

        

            TSource specification;

            return GenericHepler.ContainsSpecification<TSource>(source, predicate, out specification);

        

        #endregion       
   代码中的注释已经将各个方法的用途说得非常清楚,下面我们举两个例子来看看如何使用它们以发挥它们的威力!
   例子一:比如,我们要从当前玩家(IPlayer)列表中找出所有年龄大于30岁的玩家的ID,通常这样做:
        public IList<string> GetOldPlayer()

        

            IList<string> results = new List<string>();

            foreach (IPlayer player in this.playerList)

            

                if (player.Age > 30)

                

                    results.Add(player.ID);

                

            


            return results;

        
   如果使用上面我们封装的API,则可以非常简单地达到目的:
 public IList<string> GetOldPlayer()

 

     return GenericHepler.ConvertSpecification<IPlayer, string>(this.playerList, delegate(IPlayer player)  return player.ID;  , delegate(IPlayer player) return player.Age > 30 );            

 
  一句搞定。
   
   例子二:我们要从当前的玩家字典(Dictionary)中取出所有ID不是指定集合中的ID的其它玩家列表。
   通常,我们可以这样做:
        public IList<IPlayer> GetPartners(params string[] excludedUserIDs)

        

            IList<IPlayer> partnersList = new List<IPlayer>();

            foreach (string userID in this.dicPlayers.Keys)

            

                bool exclude = false;

                foreach (string excludedUser in excludedUserIDs)

                

                    if (userID == excludedUser)

                    

                        exclude = true;

                        break;

                    

                


                if (!exclude)

                

                    partnersList.Add(this.dicPlayers[userID]);

                

            

            return partnersList;

        
   使用上面我们封装的API,则非常简单:
 public IList<IPlayer> GetPartners(params string[] excludedUserIDs)

 

     return GenericHepler.Where<IPlayer>(this.dicPlayers.Values, delegate(IPlayer player)  return !GenericHepler.ContainsSpecification<string>(excludedUserIDs, delegate(string id)  return id == player.UserID; ); );                            

 
   灵活地使用这些API,我们可以非常简洁地操作集合中的元素。
   最后给出GenericHepler类的源码下载,其中还包含了几个未介绍的实用的API。files.cnblogs.com/zhuweisky/GenericHepler.rar
 
View Code

C#线程类的定义实例解析

技术图片
C#线程类的定义实例:

//C#线程类的定义实例  
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();  
          
      
  
    C#线程类的定义之使用MyThread类:

class NewThread : MyThread  
  
      override public void run()  
        
      Console.WriteLine("使用MyThread建立并运行线程");  
        
    
 
  static void Main(string[] args)  
    
 
      NewThread nt = new NewThread();  
      nt.start();  
    
    C#线程类的定义实例基本的内容就向你介绍到这里,希望对你了解和学习C#线程类的定义有所帮助。
View Code

概述C#线程功能

技术图片
C#线程功能

    线程是允许进行并行计算的一个抽象概念:在另一个线程完成计算任务的同时,一个线程可以对图像进行更新,二个线程可以同时处理同一个进程发出的二个网络请求。 

    从概念上讲,线程提供了一种在一个软件中并行执行代码的方式━━每个线程都“同时”在一个共享的内存空间中执行指令,(当然是在一个处理器上,这是通过处于运行状态的线程的交替执行完成的。),因此,每个线程都可以访问一个程序内的数据结构。由于这种原因,多线程编程的难度就可想而知了,因为一个程序内有许多不同的线程需要安全地共享数据。   

线程的创建和运行   

    Java在java.lang.Thread和java.lang.Runnable类中提供了大部分的C#线程功能。创建一个线程非常简单,就是扩展 Thread类,并调用start()。通过创建一个执行Runnable()的类,并将该类作为参数传递给Thread(),也可以定义一个线程。仔细地阅读下面这个简单的Java程序,其中有2个线程同时在从1数到5,并将结果打印出来。

public class ThreadingExample  
extends Object   
public static void main( String args[] )   
Thread[] threads = new Thread[2];  
for( int count=1;count<=threads.length;count )   
threads[count] = new Thread( new Runnable()   
public void run()   
count();  
  
 );  
threads[count].start();  
  
  
public static void count()   
for( int count=1;count<=5;count )  
System.out.print( count " " );  
  
 
    我们可以使用System.Threading.Thread和System.Threading.ThreadStart二个类将上述的Java程序转换为C#语言:

using System.Threading;  
public class ThreadingExample : Object   
public static void Main()   
Thread[] threads = new Thread[2];  
for( int count=1;count<=threads.Length;count )   
threads[count] = new Thread( new ThreadStart( Count ) );  
threads[count].Start();  
  
  
public static void Count()   
for( int count=1;count<=5;count )  
Console.Write( count " " );  
  
 
    这个例子中有一些小技巧。Java允许扩展java.lang.Thread类和执行java.lang.Runnable接口,C#则没有为我们提供这些便利。一个C#中的Thread对象是不可知的,必须通过ThreadStart进行创建,这意味着不能使用内部的类模式,而必须创建一个对象,而且必须传递给线程一个对象的方法供线程执行用。以上介绍C#线程功能。
View Code

 

以上是关于C#多线程编程的主要内容,如果未能解决你的问题,请参考以下文章

C#多线程编程

C#多线程编程

C#多线程编程

C#多线程编程

C#多线程编程(转)

c# 并发编程系列之三:使用 Parallel 开始第1个多线程编码