• C#当中的多线程_线程池


    3.1 简介

    线程池主要用在需要大量短暂的开销大的资源的情形。我们预先分配一些资源在线程池当中,当我们需要使用的时候,直接从池中取出,代替了重新创建,不用时候就送回到池当中。

    .NET当中的线程池是受CLR来管理的。

    .NET线程池有一个QueueUserWorkItem()的静态方法,这个方法接收一个委托,每当该方法被调用后,委托进入内部的队列中,如果线程池当中没有任何线程,此时创建一个新的工作线程,并将队列的第一个委托放入到工作线程当中。

        

     

    注意点:

    ①线程池内的操作,尽量放入短时间运行的工作

    ASP.NET应用程序使用线程池不要把工作线程全部使用掉,否则web服务器将不能处理新的请求。

    ASP.NET只推荐使用输入/输出密集型异步操作,因为其使用了一个不同的方式,叫做I/O线程。

    ③线程池当中的线程全部是后台线程,因此要注意前台线程执行完成后,后台线程也将结束工作。

     

    3.2线程池中调用委托

    首先要了解一个什么是【异步编程模型(Asynchronous Programming Model简称APM)】

    .NET 1.0 异步编程模型(APM),

    .NET 2.0 基于事件的异步编程模型(EAP),

    .NET 4.0 基于任务的异步编程模型(TAP)。

    本章主要了解什么是APMEAP

    下面这篇文章介绍了异步编程模型,感觉挺好的,这里mark一下。

    http://blog.csdn.net/xinke453/article/details/37810823

    结合我这本书上的Demo,感觉理解起来无鸭梨,哈哈~

    1     class Program
    
    2     {
    
    3         static void Main(string[] args)
    
    4         {
    
    5             int threadId = 0;
    
    6
    
    7             RunOnThreadPool poolDelegate = Test;
    
    8                         //用创建线程的方法先创建了一个线程
    
    9             var t = new Thread(() => Test(out threadId));
    
    10             t.Start();
    
    11             t.Join();
    
    12
    
    13             Console.WriteLine("Thread id: {0}", threadId);
    
    14
    
    15             /*
    
    16              使用BeginInvoke来运行委托,Callback是一个回调函数,
    
    17               "a delegate asynchronous call" 代表你希望转发给回调方法的一个对象的引用,
    
    18              在回调方法中,可以查询IAsyncResult接口的AsyncState属性来访问该对象
    
    19              */
    
    20             IAsyncResult r = poolDelegate.BeginInvoke(out threadId, Callback, "a delegate asynchronous call");
    
    21              //这个例子当中使用AsyncWaitHandle属性来等待直到操作完成★
    
    22             r.AsyncWaitHandle.WaitOne();
    
    23             //操作完成后,会得到一个结果,可以通过委托调用EndInvoke方法,将IAsyncResult对象传递给委托参数。
    
    24             string result = poolDelegate.EndInvoke(out threadId, r);
    
    25             
    
    26             Console.WriteLine("Thread pool worker thread id: {0}", threadId);
    
    27             Console.WriteLine(result);
    
    28
    
    29             Thread.Sleep(TimeSpan.FromSeconds(2));
    
    30         }
    
    31
    
    32         private delegate string RunOnThreadPool(out int threadId);
    
    33
    
    34         private static void Callback(IAsyncResult ar)
    
    35         {
    
    36             Console.WriteLine("Starting a callback...");
    
    37             Console.WriteLine("State passed to a callbak: {0}", ar.AsyncState);
    
    38             Console.WriteLine("Is thread pool thread: {0}", Thread.CurrentThread.IsThreadPoolThread);
    
    39             Console.WriteLine("Thread pool worker thread id: {0}", Thread.CurrentThread.ManagedThreadId);
    
    40         }
    
    41
    
    42
    
    43         private static string Test(out int threadId)
    
    44         {
    
    45             Console.WriteLine("Starting...");
    
    46             Console.WriteLine("Is thread pool thread: {0}", Thread.CurrentThread.IsThreadPoolThread);
    
    47             Thread.Sleep(TimeSpan.FromSeconds(2));
    
    48             threadId = Thread.CurrentThread.ManagedThreadId;
    
    49             return string.Format("Thread pool worker thread id was: {0}", threadId);

    注意:在这个例子当中,主线程调用Thread.Sleep(TimeSpan.FromSeconds(2));如果没这句话,回调函数就不会被执行了,

    以为线程池是后台线程,此时主线程结束,那么后台线程也跟着结束了,所以可能不会执行。

    对于访问异步操作的结果,APM提供了四种方式供开发人员选择:

    ①在调用BeginXxx方法的线程上调用EndXxx方法来得到异步操作的结果,但是这种方式会阻塞调用线程,知道操作完成之后调用线程才继续运行。

    ②查询IAsyncResultAsyncWaitHandle属性,从而得到WaitHandle,然后再调用它的WaitOne方法来使一个线程阻塞并等待操作完成再调用EndXxx方法来获得操作的结果。

    (本例子当中使用了这个方法)

    ③循环查询IAsyncResultIsComplete属性,操作完成后再调用EndXxx方法来获得操作返回的结果。

    ④使用 AsyncCallback委托来指定操作完成时要调用的方法,在操作完成后调用的方法中调用EndXxx操作来获得异步操作的结果。

    ★★★推荐使用第④种方法,因为此时不会阻塞执行BeginXxx方法的线程,然而其他三种都会阻塞调用线程,相当于效果和使用同步方法是一样,个人感觉根本失去了异步编程的特点,所以其他三种方式可以简单了解下,在实际异步编程中都是使用委托的方式。

    3.3向线程池中加入异步操作

    QueueUserWorkItem方法的定义!
    1     [SecuritySafeCritical] 2     public static bool QueueUserWorkItem(WaitCallback callBack);
    1 [SecuritySafeCritical] 2     public static bool QueueUserWorkItem(WaitCallback callBack, object state);
    实例:
    1     class Program
    2     {
    3         static void Main(string[] args)
    4         {
    5             const int x = 1;
    6             const int y = 2;
    7             const string lambdaState = "lambda state 2";
    8            //方法一,直接调用QueueUserWorkItem传入单个参数,作为回调函数
    9            ThreadPool.QueueUserWorkItem(AsyncOperation);
    10           Thread.Sleep(TimeSpan.FromSeconds(1));
    11 
    12           //方法二,传入回调函数以及状态参数
    13           ThreadPool.QueueUserWorkItem(AsyncOperation, "async state");
    14           Thread.Sleep(TimeSpan.FromSeconds(1));
    15 
    16           //方法三,使用labmbda表达式
    17           ThreadPool.QueueUserWorkItem( state => {
    18                     Console.WriteLine("Operation state: {0}", state);
    19                     Console.WriteLine("Worker thread id: {0}", Thread.CurrentThread.ManagedThreadId);
    20                     Thread.Sleep(TimeSpan.FromSeconds(2));
    21                 }, "lambda state");
    22 
    23           //方法四,使用闭包机制
    24           ThreadPool.QueueUserWorkItem( _ =>
    25             {
    26                 Console.WriteLine("Operation state: {0}, {1}", x+y, lambdaState);
    27                 Console.WriteLine("Worker thread id: {0}", Thread.CurrentThread.ManagedThreadId);
    28                 Thread.Sleep(TimeSpan.FromSeconds(2));
    29             }, "lambda state");
    30 
    31             Thread.Sleep(TimeSpan.FromSeconds(2));
    32         }
    33 
    34         private static void AsyncOperation(object state)
    35         {
    36             Console.WriteLine("Operation state: {0}", state ?? "(null)");
    37             Console.WriteLine("Worker thread id: {0}", Thread.CurrentThread.ManagedThreadId);
    38             Thread.Sleep(TimeSpan.FromSeconds(2));
    39         }

    扩展:C#闭包(Closure)机制是什么?

    3.4线程池与并行度

    下面这个实例展示线程池如何工作于大量异步操作,以及他和创建的大量单独线程方式的区别。

    1     class Program
    2     {
    3         static void Main(string[] args)
    4         {
    5             const int numberOfOperations = 500;
    6             var sw = new Stopwatch();
    7             sw.Start();
    8             UseThreads(numberOfOperations);
    9             sw.Stop();
    10             Console.WriteLine("Execution time using threads: {0}", sw.ElapsedMilliseconds);
    11 
    12             sw.Reset();
    13             sw.Start();
    14             UseThreadPool(numberOfOperations);
    15             sw.Stop();
    16             Console.WriteLine("Execution time using threads: {0}", sw.ElapsedMilliseconds);
    17         }
    18 
    19         static void UseThreads(int numberOfOperations)
    20         {
    21             using (var countdown = new CountdownEvent(numberOfOperations))
    22             {
    23                 Console.WriteLine("Scheduling work by creating threads");
    24                 for (int i = 0; i < numberOfOperations; i++)
    25                 {
    26                     var thread = new Thread(() => {
    27                         Console.Write("{0},", Thread.CurrentThread.ManagedThreadId);
    28                         Thread.Sleep(TimeSpan.FromSeconds(0.1));
    29                         countdown.Signal();
    30                     });
    31                     thread.Start();
    32                 }
    33                 countdown.Wait();
    34                 Console.WriteLine();
    35             }
    36         }
    37 
    38         static void UseThreadPool(int numberOfOperations)
    39         {
    40             using (var countdown = new CountdownEvent(numberOfOperations))
    41             {
    42                 Console.WriteLine("Starting work on a threadpool");
    43                 for (int i = 0; i < numberOfOperations; i++)
    44                 {
    45                     ThreadPool.QueueUserWorkItem( _ => {
    46                         Console.Write("{0},", Thread.CurrentThread.ManagedThreadId);
    47                         Thread.Sleep(TimeSpan.FromSeconds(0.1));
    48                         countdown.Signal();
    49                     });
    50                 }
    51                 countdown.Wait();
    52                 Console.WriteLine();
    53             }
    54         }
    55     }

    分别用创建大量线程的方式和线程池的方式执行500个Thread.Sleep(TimeSpan.FromSeconds(0.1))操作,

    我们发现线程池花费了更多的时间,但是占用的资源数目很少(通过ThreadId来看)。

    3.5实现一个取消选项

    使用CancellationTokenSource和CancellationToken两个类来实现工作线程工作的取消操作。

    实例:
    1     class Program
    2     {
    3         static void Main(string[] args)
    4         {
    5              using (var cts = new CancellationTokenSource())
    6              {
    7                   CancellationToken token = cts.Token;
    8                   ThreadPool.QueueUserWorkItem(_ => AsyncOperation1(token));
    9                   Thread.Sleep(TimeSpan.FromSeconds(2));
    10                  cts.Cancel();
    11             }
    12 
    13             using (var cts = new CancellationTokenSource())
    14             {
    15                  CancellationToken token = cts.Token;
    16                  ThreadPool.QueueUserWorkItem(_ => AsyncOperation2(token));
    17                  Thread.Sleep(TimeSpan.FromSeconds(2));
    18                  cts.Cancel();
    19              }
    20 
    21              using (var cts = new CancellationTokenSource())
    22              {
    23                   CancellationToken token = cts.Token;
    24                   ThreadPool.QueueUserWorkItem(_ => AsyncOperation3(token));
    25                   Thread.Sleep(TimeSpan.FromSeconds(2));
    26                   cts.Cancel();
    27              }
    28 
    29             Thread.Sleep(TimeSpan.FromSeconds(2));
    30         }
    31 
    32        /// <summary>
    33        /// 第一种采用轮询IsCancellationRequested属性的方式,如果为true,那么操作被取消
    34        /// </summary>
    35        /// <param name="token"></param>
    36         static void AsyncOperation1(CancellationToken token)
    37         {
    38             Console.WriteLine("Starting the first task");
    39             for (int i = 0; i < 5; i++)
    40             {
    41                 if (token.IsCancellationRequested)
    42                 {
    43                     Console.WriteLine("The first task has been canceled.");
    44                     return;
    45                 }
    46                 Thread.Sleep(TimeSpan.FromSeconds(1));
    47             }
    48             Console.WriteLine("The first task has completed succesfully");
    49         }
    50        /// <summary>
    51        /// 抛出一个OperationCancelledException异常
    52        /// 这个允许操作之外控制取消过程,即需要取消操作的时候,通过操作之外的代码来处理
    53        /// </summary>
    54        /// <param name="token"></param>
    55         static void AsyncOperation2(CancellationToken token)
    56         {
    57             try
    58             {
    59                 Console.WriteLine("Starting the second task");
    60 
    61                 for (int i = 0; i < 5; i++)
    62                 {
    63                     token.ThrowIfCancellationRequested();
    64                     Thread.Sleep(TimeSpan.FromSeconds(1));
    65                 }
    66                 Console.WriteLine("The second task has completed succesfully");
    67             }
    68             catch (OperationCanceledException)
    69             {
    70                 Console.WriteLine("The second task has been canceled.");
    71             }
    72         }
    73        /// <summary>
    74        /// 第三种注册一个回调函数,当操作被取消时候,调用回调函数
    75        /// </summary>
    76        /// <param name="token"></param>
    77         private static void AsyncOperation3(CancellationToken token)
    78         {
    79             bool cancellationFlag = false;
    80             token.Register(() => cancellationFlag = true);
    81             Console.WriteLine("Starting the third task");
    82             for (int i = 0; i < 5; i++)
    83             {
    84                 if (cancellationFlag)
    85                 {
    86                     Console.WriteLine("The third task has been canceled.");
    87                     return;
    88                 }
    89                 Thread.Sleep(TimeSpan.FromSeconds(1));
    90             }
    91             Console.WriteLine("The third task has completed succesfully");
    92       

    CancellationTokenSourceCancellationToken两个类是.net4.0一会引入的,目前是实现异步操作取消事实标准。

     

    3.6在线程池中使用等待事件处理器和超时

    使用线程池当中的Threadpool.RegisterWaitSingleObject类来进行事件案处理。

    RegisterWaitSingleObject的原型如下:

    1  public static RegisteredWaitHandle RegisterWaitForSingleObject(
    2           WaitHandle waitObject,
    3           WaitOrTimerCallback callBack,
    4           Object state,
    5           int millisecondsTimeOutInterval,
    6           bool executeOnlyOnce
    7           )

    参数

    waitObject

    要注册的 WaitHandle。使用 WaitHandle 而非 Mutex

    callBack

    waitObject 参数终止时调用的 WaitOrTimerCallback 委托。

    state

    传递给委托的对象。

    timeout

    TimeSpan 表示的超时时间。如果 timeout 为零,则函数测试对象的状态并立即返回。如果 timeout  -1,则函数的超时间隔永远不过期。

    executeOnlyOnce

    如果为 true,表示在调用了委托后,线程将不再在 waitObject 参数上等待;如果为 false,表示每次完成等待操作后都重置计时器,直到注销等待。

     

    返回值

    封装本机句柄的 RegisteredWaitHandle

    相信看了这些之后大家还是一头雾水,这个方法的做用是向线程池添加一个可以定时执行的方法,第四个参数millisecondsTimeOutInterval 就是用来设置间隔执行的时间,但是这里第五个参数executeOnlyOnce 会对第四个参数起作用,当它为true时,表示任务仅会执行一次,就是说它不会,像Timer一样,每隔一定时间执行一次,这个功能的话用Timer控件也可以实现

    该方法还在此基础上提供了基于信号量来触发执行任务。

    信号量也叫开关量,故名思议,它只有两种状态,不是true就是false,

    WaitHandle就是这类开关量的基础类,继承它的类有Mutex,ManualResetEvent,AutoResetEvent,一般我们使用后两个

    写法: 

            static ManualResetEvent wait2=new ManualResetEvent(false);

            static AutoResetEvent wait=new AutoResetEvent(false); 

    我们可以在将其实例化时指定开关量的初始值。(true为有信号,false为没信号)

    ManualResetEvent和AutoResetEvent的区别在于:

    前者调用Set方法后将自动将开关量值将一直保持为true,后者调用Set方法将变为true随后立即变为false,可以将它理解为一个脉冲。

    例子
    1     class Program
    2     {
    3         static void Main(string[] args)
    4         {
    5             //执行两次RunOperations操作,第一次会超时,第二次不会超时
    6             RunOperations(TimeSpan.FromSeconds(5));
    7             RunOperations(TimeSpan.FromSeconds(7));
    8         }
    9 
    10         static void RunOperations(TimeSpan workerOperationTimeout)
    11         {
    12             //定义一个ManualResetEvent信号量,初始为false
    13             using (var evt = new ManualResetEvent(false))
    14             //实例化一个CancellationTokenSource实例,用于取消操作
    15             using (var cts = new CancellationTokenSource())
    16             {
    17                 Console.WriteLine("Registering timeout operations...");
    18                 //注册超时的被调用的回调函数。
    19                 var worker = ThreadPool.RegisterWaitForSingleObject(
    20                                         evt,
    21                     (state, isTimedOut) => WorkerOperationWait(cts, isTimedOut), 
    22                                         null, 
    23                                         workerOperationTimeout, 
    24                                         true );
    25 
    26                 Console.WriteLine("Starting long running operation...");
    27                 //线程池执行WorkerOperation操作
    28                 ThreadPool.QueueUserWorkItem(_ => WorkerOperation(cts.Token, evt));
    29 
    30                 Thread.Sleep(workerOperationTimeout.Add(TimeSpan.FromSeconds(2)));
    31                 worker.Unregister(evt);
    32             }
    33         }
    34 
    35        /// <summary>
    36        /// 线程池内需要被调用的操作
    37        /// </summary>
    38        /// <param name="token"></param>
    39        /// <param name="evt"></param>
    40         static void WorkerOperation(CancellationToken token, ManualResetEvent evt)
    41         {
    42             for(int i = 0; i < 6; i++)
    43             {
    44                 if (token.IsCancellationRequested)
    45                 {
    46                     return;
    47                 }
    48                 Thread.Sleep(TimeSpan.FromSeconds(1));
    49             }
    50             //设置信号量,此时evt为true。
    51             evt.Set();
    52         }
    53 
    54         /// <summary>
    55         /// 超时时候执行的回调函数
    56         /// </summary>
    57         /// <param name="cts"></param>
    58         /// <param name="isTimedOut"></param>
    59         static void WorkerOperationWait(CancellationTokenSource cts, bool isTimedOut)
    60         {
    61             if (isTimedOut)
    62             {
    63                 cts.Cancel();
    64                 Console.WriteLine("Worker operation timed out and was canceled.");
    65             }
    66             else
    67             {
    68                 Console.WriteLine("Worker operation succeded.");
    69             }
    70      

     

    3.7在线程池中使用计时器

    使用system.Threading.Timer对象来在线程池中创建周期性调用的异步操作。

    1     class Program
    2     {
    3         static void Main(string[] args)
    4         {
    5             Console.WriteLine("Press 'Enter' to stop the timer...");
    6             DateTime start = DateTime.Now;
    7             //实例化这个timer类
    8             //一秒后执行TimerOperation这个操作,然后每隔2秒执行一次
    9             _timer = new Timer(
    10                                 _ => TimerOperation(start), 
    11                                 null, 
    12                                 TimeSpan.FromSeconds(1), 
    13                                 TimeSpan.FromSeconds(2));
    14 
    15             Thread.Sleep(TimeSpan.FromSeconds(6));
    16 
    17             //改变计时器的运行时间,一秒后执行TimerOperation,然后每隔4秒执行一次
    18             _timer.Change(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(4));
    19 
    20             Console.ReadLine();
    21 
    22             _timer.Dispose();
    23         }
    24 
    25         static Timer _timer;
    26 
    27         static void TimerOperation(DateTime start)
    28         {
    29             TimeSpan elapsed = DateTime.Now - start;
    30             Console.WriteLine("{0} seconds from {1}. Timer thread pool thread id: {2}", elapsed.Seconds, start,
    31             Thread.CurrentThread.ManagedThreadId);
    32         }
    33 

    3.8使用BackgroudWorker组件

    本小节将介绍一个异步编程模式的另一种方式,叫基于事件的异步模式(EAP

    先看一个例子吧:

    1     class Program
    2     {
    3         static void Main(string[] args)
    4         {
    5               //实例化一个BackgroundWorker类
    6               var bw = new BackgroundWorker();
    7               //获取或设置一个值,该值指示 BackgroundWorker 能否报告进度更新。
    8               bw.WorkerReportsProgress = true;
    9               //设置后台工作线程是否支持取消操作
    10             bw.WorkerSupportsCancellation = true;
    11 
    12             //给DoWork、ProgressChanged、RunWorkerCompleted事件绑定处理函数
    13             bw.DoWork += Worker_DoWork;
    14             bw.ProgressChanged += Worker_ProgressChanged;
    15             bw.RunWorkerCompleted += Worker_Completed;
    16 
    17             //启动异步操作
    18             bw.RunWorkerAsync();
    19 
    20             Console.WriteLine("Press C to cancel work");
    21             do
    22             {
    23                 if (Console.ReadKey(true).KeyChar == 'C')
    24                 {
    25                     //取消操作
    26                     bw.CancelAsync();
    27                 }
    28                 
    29             }
    30             while(bw.IsBusy);
    31         }
    32 
    33         static void Worker_DoWork(object sender, DoWorkEventArgs e)
    34         {
    35             Console.WriteLine("DoWork thread pool thread id: {0}", Thread.CurrentThread.ManagedThreadId);
    36             var bw = (BackgroundWorker) sender;
    37             for (int i = 1; i <= 100; i++)
    38             {
    39 
    40                 if (bw.CancellationPending)
    41                 {
    42                     e.Cancel = true;
    43                     return;
    44                 }
    45 
    46                 if (i%10 == 0)
    47                 {
    48                     bw.ReportProgress(i);
    49                 }
    50 
    51                 Thread.Sleep(TimeSpan.FromSeconds(0.1));
    52             }
    53             e.Result = 42;
    54         }
    55 
    56         static void Worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
    57         {
    58             Console.WriteLine("{0}% completed. Progress thread pool thread id: {1}", e.ProgressPercentage,
    59                 Thread.CurrentThread.ManagedThreadId);
    60         }
    61 
    62         static void Worker_Completed(object sender, RunWorkerCompletedEventArgs e)
    63         {
    64             Console.WriteLine("Completed threadpool thread id:{0}",Thread.CurrentThread.ManagedThreadId);
    65             if (e.Error != null)
    66             {
    67                 Console.WriteLine("Exception {0} has occured.", e.Error.Message);
    68             }
    69             else if (e.Cancelled)
    70             {
    71                 Console.WriteLine("Operation has been canceled.");
    72             }
    73             else
    74             {
    75                 Console.WriteLine("The answer is: {0}", e.Result);
    76             }
    77         }

    事件

     

    名称

    说明

     

    Disposed

    当通过调用 Dispose 方法释放组件时发生。(从 Component 继承。)

     

    DoWork

    调用 RunWorkerAsync 时发生。

    RunWorkerAsync 方法提交一个启动以异步方式运行的操作的请求。发出该请求后,将引发 DoWork 事件,该事件随后开始执行后台操作。

    如果后台操作已在运行,则再次调用 RunWorkerAsync 将引发 InvalidOperationException

     

    ProgressChanged

    调用 ReportProgress 时发生。

    public void ReportProgress

    (
        int percentProgress
    )

    percentProgress

    已完成的后台操作所占的百分比,范围从 0% 100%

    如果您需要后台操作报告其进度,则可以调用 ReportProgress 方法来引发 ProgressChanged 事件。 WorkerReportsProgress 属性值必须是 true,否则 ReportProgress 将引发 InvalidOperationException

    您需要实现一个有意义的方法,以便按照占已完成的总任务的百分比来度量后台操作的进度。

    ReportProgress 方法的调用为异步且立即返回。The ProgressChanged 事件处理程序在创建 BackgroundWorker 的线程上执行。

     

    RunWorkerCompleted

    当后台操作已完成、被取消或引发异常时发生。

     

     

    在该方法中可以知道操作是成功完成还是发生错误,亦或被取消。

    貼り付け元  <https://msdn.microsoft.com/zh-cn/library/system.componentmodel.backgroundworker.aspx

    成功完成的时候

    任务取消的时候

     

  • 相关阅读:
    tensor的维度变换
    交叉熵损失函数
    全连接层、softmax的loss
    SoftMax函数
    pytorch实现mnist识别实战
    pytorch索引与切片
    Pytorch中的torch.gather函数
    softmax函数
    Separate to Adapt Open Set Domain Adaptation via Progressive Separation论文总结
    Separate to Adapt: Open Set Domain Adaptation via Progressive Separation论文笔记
  • 原文地址:https://www.cnblogs.com/dcz2015/p/5047920.html
Copyright © 2020-2023  润新知