那正是为啥要使用线程池

目录

  • 1.1
    简介
  • 1.2
    在线程池中调用委托
  • 1.3
    向线程池中放入异步操作
  • 1.4
    线程池与并行度
  • 1.5
    实现贰个注销选项
  • 1.6
    在线程池中动用等待事件管理器及超时
  • 1.7
    使用停车计时器
  • 1.8
    使用BackgroundWorker组件
  • 参照书籍
  • 作者水平有限,要是不当应接各位商讨指正!


1.1 简介

在本章中,首要介绍线程池(ThreadPool)的使用;在C#中它叫System.Threading.ThreadPool,在使用线程池在此之前率先我们得理解一个难题,那正是为啥要使用线程池。其首要原因是创设贰个线程的代价是昂贵的,创造一个线程会花费比比较多的系统财富。

那正是说线程池是怎么着解决那些标题标吧?线程池在开班时会自动创造一定量的线程供程序调用,使用时,开拓职员并不直接分配线程,而是将急需做的干活放入线程池工作行列中,由线程池分配已部分线程举办管理,等处理完结后线程不是被销毁,而是双重临回线程池中,那样节约了创设线程的支出。

只是在使用线程池时,须求潜心以下几点,那将极度重要。

  • 线程池不符合处理长日子运作的学业,也许处理供赋予其余线程同步的课业。
  • 制止将线程池中的专门的学业线程分配给I/O首先的职分,这种职务应该利用TPL模型。
  • 如非必需,不要手动设置线程池的最小线程数和最大线程数,CL路虎极光会自动的进行线程池的扩张和减弱,手动干预往往让品质更差。

1.2 在线程池中调用委托

本节展现的是何许在线程池中什么异步的试行委托,然后将介绍叁个叫异步编制程序模型(Asynchronous
Programming Model,简单的称呼APM)
的异步编制程序方式。

在本节及事后,为了裁减代码量,在引用程序集评释地点暗中同意增添了using static System.Consoleusing static System.Threading.Thead声称,那样申明能够让大家在前后相继中一点点一些意义非常小的调用语句。

事必躬亲代码如下所示,使用了家常创立线程和APM方式来实践同八个职分。

static void Main(string[] args)
{
    int threadId = 0;

    RunOnThreadPool poolDelegate = Test;

    var t = new Thread(() => Test(out threadId));
    t.Start();
    t.Join();

    WriteLine($"手动创建线程 Id: {threadId}");

    // 使用APM方式 进行异步调用  异步调用会使用线程池中的线程
    IAsyncResult r = poolDelegate.BeginInvoke(out threadId, Callback, "委托异步调用");
    r.AsyncWaitHandle.WaitOne();

    // 获取异步调用结果
    string result = poolDelegate.EndInvoke(out threadId, r);

    WriteLine($"Thread - 线程池工作线程Id: {threadId}");
    WriteLine(result);

    Console.ReadLine();
}

// 创建带一个参数的委托类型
private delegate string RunOnThreadPool(out int threadId);

private static void Callback(IAsyncResult ar)
{
    WriteLine("Callback - 开始运行Callback...");
    WriteLine($"Callback - 回调传递状态: {ar.AsyncState}");
    WriteLine($"Callback - 是否为线程池线程: {CurrentThread.IsThreadPoolThread}");
    WriteLine($"Callback - 线程池工作线程Id: {CurrentThread.ManagedThreadId}");
}

private static string Test(out int threadId)
{
    string isThreadPoolThread = CurrentThread.IsThreadPoolThread ? "ThreadPool - ": "Thread - ";

    WriteLine($"{isThreadPoolThread}开始运行...");
    WriteLine($"{isThreadPoolThread}是否为线程池线程: {CurrentThread.IsThreadPoolThread}");
    Sleep(TimeSpan.FromSeconds(2));
    threadId = CurrentThread.ManagedThreadId;
    return $"{isThreadPoolThread}线程池工作线程Id: {threadId}";
}

运维结果如下图所示,当中以Thread初阶的为手动创造的线程输出的消息,而TheadPool为初阶线程池职务输出的新闻,Callback为APM方式运转职分达成后,履行的回调方法,可以清楚的看见,Callback的线程也是线程池的专门的学业线程。

图片 1

在上文中,使用BeginOperationName/EndOperationName方法和.Net中的IAsyncResult目的的法子被称呼异步编制程序模型(或APM方式),那样的点子被称之为异步方法。使用委托的BeginInvoke方法来运维该信托,BeginInvoke收取贰个回调函数,该回调函数会在职务管理完了后背调用,况且能够传递三个客商自定义的意况给回调函数。

今后这种APM编制程序格局用的越来越少了,更推荐使用职分并行库(Task Parallel
Library,简单的称呼TPL)
来协会异步API。

1.3 向线程池中放入异步操作

本节将介绍怎样将异步操作归入线程池中进行,而且如何传递参数给线程池中的线程。本节中重要性行使的是ThreadPool.QueueUserWorkItem()办法,该方式可将急需周转的任务通过委托的样式传递给线程池中的线程,并且同意传递参数。

行使比较轻易,演示代码如下所示。演示了线程池使用中如何传递方式和参数,最终索要专一的是采用了Lambda表达式和它的闭包机制。

static void Main(string[] args)
{
    const int x = 1;
    const int y = 2;
    const string lambdaState = "lambda state 2";

    // 直接将方法传递给线程池
    ThreadPool.QueueUserWorkItem(AsyncOperation);
    Sleep(TimeSpan.FromSeconds(1));

    // 直接将方法传递给线程池 并且 通过state传递参数
    ThreadPool.QueueUserWorkItem(AsyncOperation, "async state");
    Sleep(TimeSpan.FromSeconds(1));

    // 使用Lambda表达式将任务传递给线程池 并且通过 state传递参数
    ThreadPool.QueueUserWorkItem(state =>
    {
        WriteLine($"Operation state: {state}");
        WriteLine($"工作线程 id: {CurrentThread.ManagedThreadId}");
        Sleep(TimeSpan.FromSeconds(2));
    }, "lambda state");

    // 使用Lambda表达式将任务传递给线程池 通过 **闭包** 机制传递参数
    ThreadPool.QueueUserWorkItem(_ =>
    {
        WriteLine($"Operation state: {x + y}, {lambdaState}");
        WriteLine($"工作线程 id: {CurrentThread.ManagedThreadId}");
        Sleep(TimeSpan.FromSeconds(2));
    }, "lambda state");

    ReadLine();
}

private static void AsyncOperation(object state)
{
    WriteLine($"Operation state: {state ?? "(null)"}");
    WriteLine($"工作线程 id: {CurrentThread.ManagedThreadId}");
    Sleep(TimeSpan.FromSeconds(2));
}

运转结果如下图所示。

图片 2

1.4 线程池与并行度

在本节中,重借使采纳普通创设线程和使用线程池内的线程在职责量极大的气象下有啥分别,大家模拟了叁个现象,创制了重重例外的线程,然后分别使用普通创制线程格局和线程池格局看看有啥两样。

static void Main(string[] args)
{
    const int numberOfOperations = 500;
    var sw = new Stopwatch();
    sw.Start();
    UseThreads(numberOfOperations);
    sw.Stop();
    WriteLine($"使用线程执行总用时: {sw.ElapsedMilliseconds}");

    sw.Reset();
    sw.Start();
    UseThreadPool(numberOfOperations);
    sw.Stop();
    WriteLine($"使用线程池执行总用时: {sw.ElapsedMilliseconds}");

    Console.ReadLine();
}

static void UseThreads(int numberOfOperations)
{
    using (var countdown = new CountdownEvent(numberOfOperations))
    {
        WriteLine("通过创建线程调度工作");
        for (int i = 0; i < numberOfOperations; i++)
        {
            var thread = new Thread(() =>
            {
                Write($"{CurrentThread.ManagedThreadId},");
                Sleep(TimeSpan.FromSeconds(0.1));
                countdown.Signal();
            });
            thread.Start();
        }
        countdown.Wait();
        WriteLine();
    }
}

static void UseThreadPool(int numberOfOperations)
{
    using (var countdown = new CountdownEvent(numberOfOperations))
    {
        WriteLine("使用线程池开始工作");
        for (int i = 0; i < numberOfOperations; i++)
        {
            ThreadPool.QueueUserWorkItem(_ =>
            {
                Write($"{CurrentThread.ManagedThreadId},");
                Sleep(TimeSpan.FromSeconds(0.1));
                countdown.Signal();
            });
        }
        countdown.Wait();
        WriteLine();
    }
}

进行结果如下,可以预知使用原有的创始线程实施,速度相当慢。只花了2分钟,但是创立了500三个线程,而使用线程池相对来讲不快,花了9分钟,可是只开创了比少之又少的线程,为操作系统节省了线程和内部存款和储蓄器空间,但花了更多的时间。

图片 3

1.5 达成贰个撤回选项

在事先的小说中有涉及,纵然急需截至贰个线程的实施,那么能够利用Abort()情势,不过有那个的缘故并不引入应用Abort()方法。

这里推荐的章程是利用合作式裁撤(cooperative
cancellation)
,那是一种保障的本领来安全撤销不再须要的职责。其关键运用CancellationTokenSourceCancellationToken四个类,具体用法见上面演示代码。

以下延时期码主若是兑现了选取CancellationTokenCancellationTokenSource来贯彻任务的撤废。不过职分撤除后可以扩充二种操作,分别是:直接回到、抛出ThrowIfCancellationRequesed不行和实施回调。详细请看代码。

static void Main(string[] args)
{
    // 使用CancellationToken来取消任务  取消任务直接返回
    using (var cts = new CancellationTokenSource())
    {
        CancellationToken token = cts.Token;
        ThreadPool.QueueUserWorkItem(_ => AsyncOperation1(token));
        Sleep(TimeSpan.FromSeconds(2));
        cts.Cancel();
    }

    // 取消任务 抛出 ThrowIfCancellationRequesed 异常
    using (var cts = new CancellationTokenSource())
    {
        CancellationToken token = cts.Token;
        ThreadPool.QueueUserWorkItem(_ => AsyncOperation2(token));
        Sleep(TimeSpan.FromSeconds(2));
        cts.Cancel();
    }

    // 取消任务 并 执行取消后的回调函数
    using (var cts = new CancellationTokenSource())
    {
        CancellationToken token = cts.Token;
        token.Register(() => { WriteLine("第三个任务被取消,执行回调函数。"); });
        ThreadPool.QueueUserWorkItem(_ => AsyncOperation3(token));
        Sleep(TimeSpan.FromSeconds(2));
        cts.Cancel();
    }

    ReadLine();
}

static void AsyncOperation1(CancellationToken token)
{
    WriteLine("启动第一个任务.");
    for (int i = 0; i < 5; i++)
    {
        if (token.IsCancellationRequested)
        {
            WriteLine("第一个任务被取消.");
            return;
        }
        Sleep(TimeSpan.FromSeconds(1));
    }
    WriteLine("第一个任务运行完成.");
}

static void AsyncOperation2(CancellationToken token)
{
    try
    {
        WriteLine("启动第二个任务.");

        for (int i = 0; i < 5; i++)
        {
            token.ThrowIfCancellationRequested();
            Sleep(TimeSpan.FromSeconds(1));
        }
        WriteLine("第二个任务运行完成.");
    }
    catch (OperationCanceledException)
    {
        WriteLine("第二个任务被取消.");
    }
}

static void AsyncOperation3(CancellationToken token)
{
    WriteLine("启动第三个任务.");
    for (int i = 0; i < 5; i++)
    {
        if (token.IsCancellationRequested)
        {
            WriteLine("第三个任务被取消.");
            return;
        }
        Sleep(TimeSpan.FromSeconds(1));
    }
    WriteLine("第三个任务运行完成.");
}

运转结果如下所示,符合预期结果。

图片 4

1.6 在线程池中使用等待事件管理器及逾期

本节将介绍怎样在线程池中运用等待职务和怎么样开展过期管理,在那之中主要运用ThreadPool.RegisterWaitForSingleObject()格局,该情势允许传入二个WaitHandle指标,和须求进行的义务、超时时间等。通过运用这么些主意,可成功线程池景况下对逾期职分的处理。

演示代码如下所示,运转了三遍利用ThreadPool.RegisterWaitForSingleObject()编写超时期码的RunOperations()办法,可是所传诵的逾期时间各异,所以导致贰个自然超时和二个不会晚点的结果。

static void Main(string[] args)
{
    // 设置超时时间为 5s WorkerOperation会延时 6s 肯定会超时
    RunOperations(TimeSpan.FromSeconds(5));

    // 设置超时时间为 7s 不会超时
    RunOperations(TimeSpan.FromSeconds(7));
}

static void RunOperations(TimeSpan workerOperationTimeout)
{
    using (var evt = new ManualResetEvent(false))
    using (var cts = new CancellationTokenSource())
    {
        WriteLine("注册超时操作...");
        // 传入同步事件  超时处理函数  和 超时时间
        var worker = ThreadPool.RegisterWaitForSingleObject(evt
            , (state, isTimedOut) => WorkerOperationWait(cts, isTimedOut)
            , null
            , workerOperationTimeout
            , true);

        WriteLine("启动长时间运行操作...");
        ThreadPool.QueueUserWorkItem(_ => WorkerOperation(cts.Token, evt));

        Sleep(workerOperationTimeout.Add(TimeSpan.FromSeconds(2)));

        // 取消注册等待的操作
        worker.Unregister(evt);

        ReadLine();
    }
}

static void WorkerOperation(CancellationToken token, ManualResetEvent evt)
{
    for (int i = 0; i < 6; i++)
    {
        if (token.IsCancellationRequested)
        {
            return;
        }
        Sleep(TimeSpan.FromSeconds(1));
    }
    evt.Set();
}

static void WorkerOperationWait(CancellationTokenSource cts, bool isTimedOut)
{
    if (isTimedOut)
    {
        cts.Cancel();
        WriteLine("工作操作超时并被取消.");
    }
    else
    {
        WriteLine("工作操作成功.");
    }
}

运作结果如下图所示,与预期结果符合。

图片 5

1.7 使用电火花计时器

坚持计时器是FCL提供的贰个类,叫System.Threading.Timer,可要结果与创制周期性的异步操作。该类应用相比简单。

以下的事必躬亲代码应用了定时器,并设置了电火花计时器延时运维时间和周期时间。

static void Main(string[] args)
{
    WriteLine("按下回车键,结束定时器...");
    DateTime start = DateTime.Now;

    // 创建定时器
    _timer = new Timer(_ => TimerOperation(start), null
        , TimeSpan.FromSeconds(1)
        , TimeSpan.FromSeconds(2));
    try
    {
        Sleep(TimeSpan.FromSeconds(6));

        _timer.Change(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(4));

        ReadLine();
    }
    finally
    {
        //实现了IDispose接口  要及时释放
        _timer.Dispose();
    }
}

static Timer _timer;

static void TimerOperation(DateTime start)
{
    TimeSpan elapsed = DateTime.Now - start;
    WriteLine($"离 {start} 过去了 {elapsed.Seconds} 秒. " +
              $"定时器线程池 线程 id: {CurrentThread.ManagedThreadId}");
}

运行结果如下所示,可以预知反应计时器依据所设置的周期时间循环的调用TimerOperation()方法。

图片 6

1.8 使用BackgroundWorker组件

本节重大介绍BackgroundWorker零件的行使,该器件实际上被用来Windows窗体应用程序(Windows
Forms Application,简单称谓WPF)
中,通过它完毕的代码能够直接与UI调控器交互,越发自认和好用。

演示代码如下所示,使用BackgroundWorker来落实对数据开展测算,并且让其协助报告职业进程,匡助撤销任务。

static void Main(string[] args)
{
    var bw = new BackgroundWorker();
    // 设置可报告进度更新
    bw.WorkerReportsProgress = true;
    // 设置支持取消操作
    bw.WorkerSupportsCancellation = true;

    // 需要做的工作
    bw.DoWork += Worker_DoWork;
    // 工作处理进度
    bw.ProgressChanged += Worker_ProgressChanged;
    // 工作完成后处理函数
    bw.RunWorkerCompleted += Worker_Completed;

    bw.RunWorkerAsync();

    WriteLine("按下 `C` 键 取消工作");
    do
    {
        if (ReadKey(true).KeyChar == 'C')
        {
            bw.CancelAsync();
        }

    }
    while (bw.IsBusy);
}

static void Worker_DoWork(object sender, DoWorkEventArgs e)
{
    WriteLine($"DoWork 线程池 线程 id: {CurrentThread.ManagedThreadId}");
    var bw = (BackgroundWorker)sender;
    for (int i = 1; i <= 100; i++)
    {
        if (bw.CancellationPending)
        {
            e.Cancel = true;
            return;
        }
        if (i % 10 == 0)
        {
            bw.ReportProgress(i);
        }

        Sleep(TimeSpan.FromSeconds(0.1));
    }

    e.Result = 42;
}

static void Worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    WriteLine($"已完成{e.ProgressPercentage}%. " +
              $"处理线程 id: {CurrentThread.ManagedThreadId}");
}

static void Worker_Completed(object sender, RunWorkerCompletedEventArgs e)
{
    WriteLine($"完成线程池线程 id: {CurrentThread.ManagedThreadId}");
    if (e.Error != null)
    {
        WriteLine($"异常 {e.Error.Message} 发生.");
    }
    else if (e.Cancelled)
    {
        WriteLine($"操作已被取消.");
    }
    else
    {
        WriteLine($"答案是 : {e.Result}");
    }
}

运行结果如下所示。

图片 7

在本节中,使用了C#中的别的贰个语法,叫事件(event)。当然这里的平地风波分歧于此前在线程同步章节中涉嫌的平地风波,这里是观看者设计方式的体现,包括事件源、订阅者和事件管理程序。由此,除了异步APM情势意外,还应该有传说事件的异步情势(伊夫nt-based
Asynchronous Pattern,简单称谓 EAP)

参照书籍

本文首要参谋了以下几本书,在这里对这个笔者表示真诚的感激你们提供了那般好的材质。

  1. 《CLR via C#》
  2. 《C# in Depth Third Edition》
  3. 《Essential C# 6.0》
  4. 《Multithreading with C# Cookbook Second Edition》
  5. 《C#二十多线程编制程序实战》

源码下载点击链接
示范源码下载

小编水平有限,假使不当迎接各位商量指正!

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*
*
Website