6搞懂线程池(二)

抱歉各位多线程专栏托更这么久,这篇文章我们继续讲线程池的相关知识,其中将涉及到如下知识:

  1. 取消异步操作
  2. 等待事件处理器及超时
  3. 计时器
  4. BackgrjavaeeoundWorker

零、取消异步操作

这一小节将引入两个类 CancellationTokenSource 和 CancellationToken 。这两个类是在 .NET 4.0 中被引入的,因此如果需要使用这两个类我们必须在 .c#javaNET 4.0 及其以上版本中使用,目前是取消异步操作的标准。下面我们通过厨师做饭,中途撤销订单的例子来看一下java这两个类具体该怎么用。

using System.Threading;
using static System.Console;
using static System.Threading.Thread;
namespace NoSix
{
    class Program
    {
        static void Main(string[] args)
        {
            using(var cts=new CancellationTokenSource())
            {
                CancellationToken token = cts.Token;
                ThreadPool.QueueUserWorkItem(_ => Cookie(token));
                Sleep(2000);
                cts.Cancel();
            }
            Read();
        }
        static void Cookie(CancellationToken token)
        {
            WriteLine("开始做饭.......");
            for (int i = 0; i < 5; i++)
            {
                if (token.IsCancellationRequested)
                {
                    WriteLine("取消做饭");
                    return;
                }
                Sleep(2000);
            }
            WriteLine("我做完饭了");
        }
    }
}

在上面的代码c#面试题中我们在 Cookie 方法中通过轮询的方式来检查 CancellationToken.IsCancellationRequesc#是什么ted 属性。如果该属性为 true ,则说java面试题明操作需要被取c#消,我们必须放弃该操作。下面我们将 Cookie 方法修改一下,用另一种方式来实现取消操作c#委托

static void Cookie(CancellationToken token)
{
    try
    {
        WriteLine("开始做饭.......");
        for (int i = 0; i < 5; i++)
        {
            token.ThrowIfCancellationRequested();
            Sleep(2000);
        }
        WriteLine("我做完饭了");
    }
    catch(OperationCanceledException)
    {
        WriteLine("取消做饭");
    }
}

这种方法我们抛出一个OperationCancelledExceptionjavaee常。这允许我们在线程池之外控制取消java编译器执行过程。需要取消操作时通过操作之外的代码来处理。下面我们再来修改一下 Cookie 方法,用第三种方法来是先取消操javascript百炼成仙作。

static void Cookie(CancellationToken token)
{
    WriteLine("开始做饭.......");
    bool cancellationFlag = false;
    token.Register(() => cancellationFlag = true);
    for (int i = 0; i < 5; i++)
    {
        if (cancellationFlag)
        {
            WriteLine("取消做饭");
            return;
        }
        Sleep(2000);
    }
    WriteLine("我做完饭了");
}

第三种方式是注册一个回调函数。javaee操作被取消时线程池将调用该回调函数。.NET 可以链式的传递一c#和java个取消逻辑到另一个异步操c#委托作中。

一、等待事件处理器及超时

在线程池中存在一个非常棒的方法 RegisterWaitForSingleObject 。它允许我们把回调函数放入线程池,每当等待事件处理器收到信号或者等待超时时将执行这个回调函数。下面的代码通过模拟初始等待下单做饭,到了下班时间(超时)后就停止接单。

using System;
using System.Threading;
using static System.Console;
using static System.Threading.Thread;
namespace RegisterWaitForSingleObject
{
    class Program
    {
        static void Main(string[] args)
        {
            Cookie(TimeSpan.FromSeconds(5));
            Cookie(TimeSpan.FromSeconds(7));
            Read();
        }
        static void Cookie(TimeSpan timeSpan)
        {
            using (var evt = new ManualResetEvent(false))
            using (var cts = new CancellationTokenSource())
            {
                WriteLine("等待做饭");
                var cookie = ThreadPool.RegisterWaitForSingleObject(evt, (state, isTimeOut) => CookieWait(cts, isTimeOut), null, timeSpan, true);
                ThreadPool.QueueUserWorkItem(_ => WorkOperation(cts.Token, evt));
                Sleep(2000);
                cookie.Unregister(evt);
            }
        }
        private static void WorkOperation(CancellationToken token, ManualResetEvent evt)
        {
            for (int i = 0; i < 6; i++)
            {
                if (token.IsCancellationRequested)
                {
                    return;
                }
                Sleep(1000);
            }
            evt.Set();
        }
        private static void CookieWait(CancellationTokenSource cts, bool isTimeOut)
        {
            if (isTimeOut)
            {
                cts.Cancel();
                WriteLine("我下班了!!!");
            }
            else
            {
                WriteLine("开始做饭!!!");
            }
        }
    }
}

我们注册了处理超时的异步操作。当接收到了 ManualRestEvent 对象的信号,工作者操作成功完成后会发出信号。如果操作完成之c#怎么读前超时javascript,那java怎么读么会使用 CancellationToken 来取消第一个操作。我们向线程池中放入一个耗c#为什么用的人很少时长的操作。它会运行 6 秒钟,如果成功完成则会设置一个 ManualRec#setEvec#怎么读nt 信号类。在其他情况下&java培训#xff0c;比如需要取消该操作,那么该操作会c#接口被丢弃。最后,为操作提供5秒的超时时间是不够的。这是因为操作会花费 6 秒来完成,只能取消该操作。所以如果提供 7 秒的超时时间是可行的,c#教程该操作会顺利完成。在有大量线程处于阻塞状态等待线程事件信号时这种方式非常有用。

二、计时器

我们前面所讲的都是一次性调用,那么如何进行周期性调用呢?这时我们就用到了计时器功能&#c#怎么读xff0c;下面我们通过例子来看一下。

using System;
using System.Threading;
using static System.Console;
using static System.Threading.Thread;
namespace _Timer_
{
    class Program
    {
        static void Main(string[] args)
        {
            WriteLine("点击回车暂停计时器");
            timer = new Timer(_ => TimerOpration(DateTime.Now), null, 1000, 2000);
            try
            {
                Sleep(6000);
                timer.Change(1000, 4000);
                Read();
            }
            finally
            {
                timer.Dispose();
            }
        }
        static Timer timer;
        static void TimerOpration(DateTime dateTime)
        {
            TimeSpan elapsed = DateTime.Now - dateTime;
            WriteLine($"{elapsed.Seconds} {dateTime} {CurrentThread.ManagedThreadId}");
        }
    }
}

我们首先创建 TimerOpration 方法传递java一个起始时间,在方法中我们计算运行的时间c#怎么读差,并打印出来。同时我们打印出起始时间和进程 ID 。然后我们在主方c#是什么法中初始化 Timer,第一个参数传入的时一个 lambda 表达式,它会在线程池中被执行。第二个参数时 null,是因为我们不需要知道用户状态对象。接着第三个参数指定了调用 TimerOpration 之前延迟的时间,也就是说延迟 N 秒后执行第一次。第四个参数代表间隔多久执行一次 TimerOpration 。最后我们 6 秒后我们修改计时器,在调用 Change 一秒后启动运行 TimerOpration 方法,以后每间隔 4 秒运行一次。

三、BackgroundWorkjava培训er

在这一小节我们将不使用线程池和委托javascript百炼成仙而是使用了事件。事件表示了一c#面试题些通知的源或当通知到达时会有所响应的一系列订java阅者。下面我们先来看一下例子。

using System;
using System.ComponentModel;
using static System.Console;
using static System.Threading.Thread;
namespace Background_Worker
{
    class Program
    {
        static void Main(string[] args)
        {
            BackgroundWorker bw = new BackgroundWorker();
            bw.WorkerReportsProgress = true;
            bw.WorkerSupportsCancellation = true;
            bw.DoWork += DoWork;
            bw.ProgressChanged += ProgressChanged;
            bw.RunWorkerCompleted += CompletedChanged;
            bw.RunWorkerAsync();
            WriteLine("输入E取消");
            do
            {
                if(ReadKey(true).KeyChar=='E')
                {
                    bw.CancelAsync();
                }
            } while (bw.IsBusy);
        }
        static void DoWork(object sender, DoWorkEventArgs e)
        {
            WriteLine($"DoWork 线程池线程ID: {CurrentThread.ManagedThreadId}");
            BackgroundWorker 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(100);
            }
            e.Result = 42;
        }
        static void ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            WriteLine($"{e.ProgressPercentage} 已完成 。Progress 线程池线程ID: {CurrentThread.ManagedThreadId}");
        }
        static void CompletedChanged(object sender, RunWorkerCompletedEventArgs e)
        {
            WriteLine($"Completed 线程池线程ID: {CurrentThread.ManagedThreadId}");
            if (e.Error != null)
            {
                WriteLine($"异常信息: {e.Error.Message} ");
            }
            else if (e.Cancelled)
            {
                WriteLine($"操作被取消");
            }
            else
            {
                WriteLine($"答案是: {e.Result}");
            }
        }
    }
}

上述代码中我们创建了 BackgroundWorker 组件的实例。显式指出该后台工作者线程支持取消操作及该操作进度的通知。我们java还定义了三个c#接口事件,当事件发生时会调用响应的事件处理器。每当事件通知订阅者时就会将具有特殊的定义签名的方法将被调用。我们可以只启动一个异步操作然后订阅给不同的事件。事件在操作执行时会被触发,这种方式被称为基于事件的异java步模式。我们定义的 DoWorjavascriptk 事件会在后台工作对象通过 RunWorkerAsync 方法启动一个异步c#教程操作时被调用。我们在得到结果后c#教程将结果设置给事件参数,接着会运行 RunWorkerCompleted 事件处理器。在该方法中可以知道操作是成功完成、发生错误c#程序设计或被取消。BackgroundWorker 主c#教程要用于 WPF 中,java培训通过后台工作事件处理器代码可以直接与 UI 控制器交互。与直接在线程池中与 UIjava模拟器 控制器交互的方式相比较&#javascriptxff0c;使用 BackgroundWorker 更好。