当前位置: 首页 > news >正文

计算限制的异步操作

一、线程池

二、执行上下文

三、协作式取消和超时

四、任务

五、并行语言集成查询(PLINQ

六、执行定时计算限制操作

一、线程池

1,CLR初始化时,线程池中是没有线程的。在内部,线程池维护了一个操作请求队列

2,每CLR一个线程池,这个线程池有CLR控制的所有AppDomain共享。如果一个进程加载了多个CLR,那么每个CLR都有它自己的线程池
3,要将一个异步的计算限制操作放到线程池的队列中,通常使用ThreadPool类定义的以下方法之一

        public static bool QueueUserWorkItem(WaitCallback callBack);
        public static bool QueueUserWorkItem(WaitCallback callBack, object state);

这些方法向线程池的队列中加一个“工作项”(callBack)以及可选的状态数据。然后,所有方法会立即返回。无state参数的那个版本向回调方法传递一个null

        static void Main(string[] args)
        {
            ThreadPool.QueueUserWorkItem(ComputeBoundOp, 5);
        }

        private static void ComputeBoundOp(Object state)
        {
            Console.WriteLine(state);
       //这个方法返回后,线程回到池中,等待另一个任务 }

 

二、执行上下文

1,每个线程都关联一个执行上下文数据结构。
2,执行上下文包括
①安全设施(压缩栈、Thread的Principal、Windwos身份)
②宿主设置(参见System.Threading.HostExecutionContextManager)
③逻辑调用上下文数据(System.Runtime.Remoting.Messaging.CallContext的LogicalGetData和LogicalSetData方法)
3,每当一个线程(初始线程)使用另一个线程(辅助线性)执行任务时,前者的执行上下文应该流向(复制到)辅助线性。这会对性能造成影响。
4,System.Threading.ExecutionContext类控制线程的执行上下文如果从一个线程“流”向另一个

    public sealed class ExecutionContext : IDisposable, ISerializable
    {
        [SecurityCritical]
        public static AsyncFlowControl SuppressFlow();//阻止流
        public static void RestoreFlow();//恢复流
        public static Boolean IsFlowSuppressed();//是否阻止流
    }
        static void Main(string[] args)
        {
            //将一些数据方法Main线程的逻辑调用上下文中
            CallContext.LogicalSetData("Name", "Hunter");

            //执行上下文流向辅助线程,辅助线程可以访问逻辑调用上下文的数据
            ThreadPool.QueueUserWorkItem(r => Console.WriteLine(CallContext.LogicalGetData("Name")));

            //现在,阻止Main线程的执行上下文
            ExecutionContext.SuppressFlow();

            //辅助线程不能访问逻辑调用上下文的数据
            ThreadPool.QueueUserWorkItem(r => Console.WriteLine(CallContext.LogicalGetData("Name")));

            //恢复Main线程的执行上下文的流动,以免将来使用更多的线程池线程
            ExecutionContext.RestoreFlow();

            ThreadPool.QueueUserWorkItem(r => Console.WriteLine(CallContext.LogicalGetData("Name")));

            //输出:
            //Hunter
            //
            //Hunter
            Console.ReadLine();
        }

 

三、协作式取消和超时

1,CancellationTokenSource

        static void Main(string[] args)
        {
            //取消操作首先创建此对象
            CancellationTokenSource cts = new CancellationTokenSource();

            ThreadPool.QueueUserWorkItem(r => Count(cts.Token));

            Thread.Sleep(2000);

            //如果Count方法已返回,Cancel没有任何效果
            cts.Cancel();//取消操作

            //输出:
            //0
            //1
            //2
            //取消了
            Console.ReadLine();
        }

        private static void Count(CancellationToken token)
        {
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine(i);

                if (token.IsCancellationRequested)//是否取消了
                {
                    Console.WriteLine("取消了");
                    break;
                }
                Thread.Sleep(1000);
            }

        }

要执行一个不允许被取消的操作,可向该操作传递通过调用CancellationToken.None属性而返回的CancellationToken,该属性返回的CancellationToken不和任何CancellationTokenSource对象关联(实例的私有字段为null)

 2,取消回调

        static void Main(string[] args)
        {
            //取消操作首先创建此对象
            CancellationTokenSource cts = new CancellationTokenSource();
            cts.Token.Register(() => Console.WriteLine("取消回调1"));
            cts.Token.Register(() => Console.WriteLine("取消回调2"));

            cts.Cancel();//取消操作

            //输出:
            //取消回调2
            //取消回调1
            Console.ReadLine();
        }

①useSynchronizationContext参数:

false:调用Cancel的线程会顺序调用已登记的所有方法

true:回调方法会被send给已捕捉的useSynchronizationContext对象

②CancellationTokenSource的Cancel方法参数

true:抛出了未处理异常的第一个方法会阻止其他回调方法的执行,抛出的异常会从Cancel中抛出

false:所有的回调方法都会执行。所有未处理的异常会被添加到一个集合中,抛出的异常会从Cancel中抛出(AggregateException)

3,向一个CancellationTokenSource登记两个回调

        static void Main(string[] args)
        {
            //创建一个CancellationTokenSource
            var cts1 =new CancellationTokenSource();
            cts1.Token.Register(() => Console.WriteLine("cts1 取消了"));

            //创建另一个创建一个CancellationTokenSource
            var cts2 = new CancellationTokenSource();
            cts2.Token.Register(() => Console.WriteLine("cts2 取消了"));

            //创一个新的CancellationTokenSource,它在cts1或cts2取消时取消
            var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cts1.Token, cts2.Token);
            linkedCts.Token.Register(() => Console.WriteLine("linkedCts 取消了"));

            cts2.Cancel();

            Console.WriteLine(cts1.IsCancellationRequested+" "+cts2.IsCancellationRequested+" "+linkedCts.IsCancellationRequested);

            //输出:
            //linkedCts 取消了
            //cts2 取消了
            //Flase True True
            Console.ReadLine();
        }

4,超时取消

        static void Main(string[] args)
        {
            
            //创建另一个创建一个CancellationTokenSource
            var cts2 = new CancellationTokenSource();
            cts2.Token.Register(() => { Console.WriteLine("cts2 取消了"); });
            cts2.CancelAfter(1000);//如果1秒钟还没有执行到cts2.Cancel()取消,则自动取消

            Thread.Sleep(100000);
            
            cts2.Cancel();

            //1秒中之后程序回输出:cts2 取消了
            Console.ReadLine();
        }

 

四、任务

ThreadPool.QueueUserWorkItem()没有内建机制让你知道操作在什么时候完成,也没有机制在完成操作时获得返回值

            ThreadPool.QueueUserWorkItem(r => { }); //调用QueueUserWorkItem
            new Task(r => { Console.WriteLine(r); }, 5).Start(); //用Task来做相同的事情。输出5
            Task.Run(() => { }); //另一个等价写法

可选择向构造器传递一些TaskCreationOptions标志来控制Task的执行方式

        [Flags,Serializable]
        public enum TaskCreationOptions
        {
            //默认
            None = 0x0000,
            //提议TaskScheduler你希望该任务尽快执行(给你的感觉就像queue的感觉)
            PreRunning = 0x0001,
            //提议TaskScheduler应尽可能地创建线程池线程(如果是长时间运行的任务,建议使用此选项,使用此选项会创建一个线程,还不是使用线程池)
            LongRunning = 0x0002,
            //该提议总是被采纳:将一个Task和它的父Task关联
            AttachedToParent=0x0004,
            //该提议总是被采纳:如果一个任务视图和这个父任务链接,它就是一个普通任务,而不是子任务
            DenyChildAttach = 0x0008,
            //该提议总是被采纳:强迫子任务使用默认调度器而不是父任务的调度器
            HideScheduler = 0x0010,
        }

 1,等待任务完成并获取结果

        static void Main(string[] args)
        {
            //创建一个任务,现在还没有开始运行
            Task<int> t1 = new Task<int>(() => 1);

            //可以后再启动任务
            t1.Start();

            //可选择显示等待任务完成
            t1.Wait();

            Console.WriteLine(t1.Result);//输出:1

            Console.ReadLine();
        }

任务抛出异常会被吞噬并存储到一个集合中,而线程池线程可以返回到线程池中。调用Wait方法或者Result属性时,这些成员会抛出一个System.AggregateException对象

除了等待单个任务,还可以等待一个Task对象数组

            //会阻塞调用线程,直到数组中的任何Task对象完成
            //方法返回Int32数组索引值,指明完成的是哪个Task对象
            //方法返回后。线程被唤醒并继续运行
            //如果发生超时,方法返回-1
            //如果WaitAny通过一个CancellationToken取消,会抛出一个OperationCanceledException
            Task.WaitAny(task1, task2);

            //会阻塞调用线程,直到数组中的所有Task对象完成
            //所有Task对象都完成,WaitAll返回true
            //如果发生超时,方法返回false
            //如果WaitAll通过一个CancellationToken取消,会抛出一个OperationCanceledException
            Task.WaitAll(task1, task2);

2,取消任务

        static void Main(string[] args)
        {

            CancellationTokenSource cts = new CancellationTokenSource();

            Task<Int32> t = Task.Run(() => Sum(cts.Token, 100), cts.Token);

            //在之后的某个时间,取消CancellationTokenSource以取消Task
            cts.Cancel();

            try
            {
                //如果任务已取消,Result会抛出一个AggregateException异常
                Console.WriteLine(t.Result);
            }
            catch (AggregateException x)
            {
                //将任何OperationCanceledException对象都视为已处理
                //其他任何异常都造成抛出一个新的AggregateException
                //其中只包含未处理的异常
                x.Handle(e => e is OperationCanceledException);
                //所有异常都处理好之后,执行下面这一行
                Console.WriteLine("计算已取消");
            }

            Console.ReadLine();
        }


        private static Int32 Sum(CancellationToken token, Int32 n)
        {
            int num = 0;
            for (; n > 0; n--)
            {
                //如果任务取消,则抛出OperationCanceledException异常
                //这样设计是因为Task会捕获异常
                token.ThrowIfCancellationRequested();

                checked
                {
                    num += n;
                }
            }
            return num;
        }

4,任务完成时自动启动新任务 

TaskContinuationOptions.OnlyOnCanceled//第一个任务被取消时才执行
TaskContinuationOptions.OnlyOnFaulted//第一个任务被抛异常时才执行
TaskContinuationOptions.OnlyOnRanToCompletion//第一个任务顺利完成时才执行

        [Flags, Serializable]
        public enum TaskContinuationOptions
        {
            //默认
            None = 0x0000,
            //提议TaskScheduler你希望该任务尽快执行
            PreferRunning = 0x0001,
            //提议TaskScheduler应尽可能地创建线程池线程
            LongRunning = 0x0002,
            //AttachedToParent:将一个Task和它的父Task关联
            AttachedToParent = 0x0004,
            //该提议总是被采纳:如果一个任务视图和这个父任务链接,它就是一个普通任务,而不是子任务
            DenyChildAttach = 0x0008,
            //该提议总是被采纳:强迫子任务使用默认调度器而不是父任务的调度器
            HideScheduler = 0x0010,
            //除非前置任务(antecedent task)完成,否则精致延续任务完成(取消)
            LazyCancellation = 0x0020,

            //这个标志指出你希望由执行第一个任务的线程执行ContinueWith任务。
            //第一个任务完成后,调用ContinueWith的线程接着执行ContinueWith任务
            ExecuteSynchronously = 0x80000,

            //这些标志指出在什么情况下运行ContinueWith任务
            NotOnRanToCompletion = 0x10000,
            NotOnFaulted = 0x20000,
            NotOnCanceled = 0x40000,

            //这些标志是以上三个标志的遍历组合
            OnlyOnCanceled = NotOnRanToCompletion | NotOnFaulted,//第一个任务被取消时才执行
            OnlyOnFaulted = NotOnRanToCompletion | NotOnCanceled,//第一个任务被抛异常时才执行
            OnlyOnRanToCompletion = NotOnFaulted | NotOnCanceled//第一个任务顺利完成时才执行
        }
        static void Main(string[] args)
        {

            var t1 = Task.Run(() =>Console.WriteLine("第一个任务"));
            t1.ContinueWith(task => Console.WriteLine("任务顺利完成时执行"), TaskContinuationOptions.OnlyOnRanToCompletion);
            t1.ContinueWith(task => Console.WriteLine("任务抛异常时执行"), TaskContinuationOptions.OnlyOnFaulted);
            t1.ContinueWith(task => Console.WriteLine("任务取消时执行"), TaskContinuationOptions.OnlyOnCanceled);
            //输出:
            //第一个任务
            //任务顺利完成时执行

            Console.ReadLine();
        }

5,任务可以启动子任务

        static void Main(string[] args)
        {
            //提高性能,一个任务泵本需要3秒执行完,使用子任务只需要1秒多就可以执行完
            Task<Int32[]> parent = new Task<Int32[]>(() =>
            {
                var results = new int[3];

                //创建并启动3个子任务
                new Task(() =>
                {
                    Thread.Sleep(1000);
                    results[0] = 1;
                }, TaskCreationOptions.AttachedToParent).Start();
                new Task(() =>
                {
                    Thread.Sleep(1000);
                    results[1] = 2;
                }, TaskCreationOptions.AttachedToParent).Start();
                new Task(() =>
                {
                    Thread.Sleep(1000);
                    results[2] = 3;
                }, TaskCreationOptions.AttachedToParent).Start();

                return results;
            });

            //父任务及其子任务运行完成后,用一个延续任务显示结果
            parent.ContinueWith(parentTask => Array.ForEach(parentTask.Result, Console.WriteLine));

            //启动父任务,便于它启动它的子任务
            parent.Start();

            Console.ReadLine();
        }

 TaskCreationOptions.AttachedToParent标志将一个Task和创建它的Task关联,结果是除非所有子任务(以及子任务的子任务)结束运行,否则创建任务(父任务)不认为已经结束

6,任务内部揭秘

①每个Task对象都有一组字段。必须为这些字段分配内存
Int32 ID(只读唯一标识,首次查询此字段时分配)
代表Task执行状态的一个Int32
对父任务的引用
对Task创建时指定的TaskScheduler的引用
对回调方法的引用
对要传给回调方法的对象的引用(可通过Task的只读AsyncState属性查询)
对ExecutionContext的引用
对ManualResetEventSlim对象的引用

 

②Task生存期状态

    //这些标志指出一个Task在其生命周期内的状态
    public enum TaskStatus
    {
        Created,//任务以显示创建;可以手动Start()这个任务
        WaitingForActivation,//任务以隐式创建;会自动开始
        WaitingToRun,//任务以调度,但尚未运行
        Running,//任务正在运行
        WaitingForChildrenToComplate,//任务正在等待它的子任务完成,子任务完成后它才完成

        //任务最重状态时以下三个之一
        RanToComplation,//运行完成
        Canceled,//取消
        Faulted//出错
    }

首次构造Task对象时,状态为Created
当任务启动时,它的状态变成WaitingToRun
Task实际在一个线程上运行时,它的状态变成Runting
任务停止运行并等待它的任何子任务时,状态变成WaitingForChildrenToComlate
任务完成时变成以下状态之一:RanToComplate(运行完成)、Canceled(取消)、Faulted(出错)
调用ContinueWith、ContinueWhenAll、ContinueWhenAny或FromAsync等方法创建的Task对象出于WaitingForActivation状态
通过TaskComplationSource<TResult>对象创建的Task也出于WaitingForActivation状态

 

7,任务工厂

为什么要使用任务工厂:有时需要创建一组共享相同配置的Task对象,为避免机械地将相同的参数传给每个Task的构造器

        static void Main(string[] args)
        {
            Task parent = new Task(() =>
            {
                var cts = new CancellationTokenSource();
                var tf=new TaskFactory<Int32>(
                    cts.Token,//每个task都共享相同的CancellationTokenSource标记
                    TaskCreationOptions.AttachedToParent, //任务都被视为父任务的子任务
                    TaskContinuationOptions.ExecuteSynchronously, //TaskFactory创建的所有延续任务都以同步方式执行
                    TaskScheduler.Default//使用默认的任务调度器
                    );

                //这个任务创建并启动3个子任务
                var childTasks = new[]
                {
                    tf.StartNew(() => 1),
                    tf.StartNew(() => 2),
                    tf.StartNew(() => { throw new ArgumentNullException();})
                };


                //任何子任务抛出异常,则终止所有子任务
                foreach (var childTask in childTasks)
                {
                    childTask.ContinueWith(t => cts.Cancel(), TaskContinuationOptions.OnlyOnFaulted);
                }

                //输出子任务值合计
                tf.ContinueWhenAll(childTasks,
                    //该子任务不受 取消 影响
                    complatedTasks => complatedTasks.Where(r => !r.IsFaulted && !r.IsCanceled).Sum(r => r.Result),CancellationToken.None)
                    .ContinueWith(r => Console.WriteLine(r.Result), TaskContinuationOptions.ExecuteSynchronously);
            });


            //上个任务有异常时执行输出抛出异常的类型
            parent.ContinueWith(p =>
            {
                StringBuilder sb = new StringBuilder();
                foreach (var e in p.Exception.Flatten().InnerExceptions)
                {
                    sb.Append(e.GetType().ToString() + Environment.NewLine);
                }
                Console.WriteLine(sb.ToString());

            },TaskContinuationOptions.OnlyOnFaulted);


            parent.Start();
            Console.ReadLine();
        }

 

8,任务调度器

FCL提供了两个派生自TaskScheduler的类型:

①线程池任务调度器(默认使用)

②同步上下文任务调度器

同步上下文任务调度器提供了图形用户界面的应用程序,例如Windows窗体等。它将所有任务都调度给应用程序的GUI线程,是所有任务代码都能成功更新UI组件(按钮、菜单项等)

        private void Form1_Load(object sender, EventArgs e)
        {
            //TaskScheduler.FromCurrentSynchronizationContext()使用同步上下文任务调度器
            Task.Run(() => { label1.Text = "同步上下文任务调度器(失败)"; }, CancellationToken.None)
                .ContinueWith(task => { label1.Text = "同步上下文任务调度器(成功)"; }, CancellationToken.None,
                    TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.FromCurrentSynchronizationContext());
        }

 

8,Parallel的静态For,ForEach,Invoke方法

1>使用Parallel的前提条件:

①工作项必须能并行执行

②避免修改共享数据的工作项

2>Parallel的性能开销:

①委托对象必须分配,而针对每个工作项都要调用一次这些委托

②如果为处理非常快的工作项使用Parallel的方法,则会得不偿失

        static void Main(string[] args)
        {

            //最后输出Complate。线程要在所有工作完成后才继续运行。如果存在异常,则抛出AggregateException
            
            //使用For执行更快
            Parallel.For(0, 100, r => Console.WriteLine(r));

            Parallel.ForEach(new[] {1, 2}, r => { Console.WriteLine(r); });

            Parallel.Invoke(()=>Method1(), () => Method2());

            Console.WriteLine("Complate");
            Console.ReadLine();
        }

3>Parallel的静态For,ForEach,Invoke方法都提供了此参数

    
    public class ParallelOptions
    {
        public ParallelOptions();

        //允许取消操作(默认为CancellationToken.None)
        public CancellationToken CancellationToken { get; set; }
        //允许指定要使用哪个TaskScheduler(默认为TaskScheduler.Default)
        public int MaxDegreeOfParallelism { get; set; }

    }

 4>Parallel的静态For,ForEach方法有一些重载版本允许传递3个委托

①任务局部初始化委托(localInit):为参与工作的每个任务都调用一次改委托。这个委托是在任务被要求处理一个工作项之前调用的

②主体委托(body):为参与工作的各个线程所处理的每一项都调用一次该委托

③任务局部终结委托(localFinally):为参与工作的每一个任务都调用一次改委托。这个委托实在任务处理好派发给它的所有工作项之后调用的。即使主体委托代码引发一个未处理的异常,也会调用它

        static void Main(string[] args)
        {

            Console.WriteLine(DirectoryBytes(@"E:\web", "*", SearchOption.TopDirectoryOnly));
            
            Console.ReadLine();
        }

        private static long DirectoryBytes(string path, string searchPattern, SearchOption searchOption)
        {
            var files = Directory.EnumerateFiles(path, searchPattern, searchOption);
            long masterTotal = 0;
            ParallelLoopResult result = Parallel.ForEach<string, long>(files,
                () =>
                {
                    //localInit:每个任务开始之前调用一次
                    //每个任务开始之前,总计值都初始化为0
                    return 0;
                },
                (file, loopState, index, taskLocalTotal) =>//taskLocalTotal接受localInit的返回值
                {
                    //body:每个工作项调用一次
                    //获得这个文件的大小,把它添加到这个任务的累加值上
                    long fileLength = 0;
                    FileStream fs = null;
                    try
                    {
                        fs = File.OpenRead(file);
                        fileLength = fs.Length;
                    }
                    catch (IOException)
                    {
                        //忽略拒绝访问的文件
                    }
                    finally
                    {
                        if (fs != null) fs.Dispose();
                    }

                    return taskLocalTotal + fileLength;
                },
                taskLocalTotal =>//taskLocalTotal接受body的返回值
                {
                    //localFinally:每个任务完成时调用一次
                    //将这个任务的总计值(taskLocalTotal)加到总的总计值(masterTotal)上
                    Interlocked.Add(ref masterTotal, taskLocalTotal);
                });
            return masterTotal;
        }
    }

loopState:

    public class ParallelLoopState
    {
        //告诉循环停止处理任何跟多的工作
        public void Stop();
        //停止之后该属性为true
        public bool IsStopped { get; }
        //告诉循环不再继续处理当前项之后的项
        public void Break();
        //该属性返回在处理过程中调用过Break方法的最低的项(从来没有调用过,则返回null)
        public Int64? LowestBreakInteration { get; }
        //任何项造成未处理的异常,则为true
        public bool IsException { get; }
        //调用过Stop、Break、取消过CancellationTokenSource,造成未处理异常。则为true
        public bool ShouldExitCurrentInteration { get; }
    }

ParallelLoopResult:

    public struct ParallelLoopResult
    {
        //如果操作提前终止,则返回false
        public bool IsComplated { get; }
        public long? LowestBreakInteration { get; }
    }

IsComplated=true 循环运行完成,所有项得到了处理
IsComplated=false LowestBreakInteration=null 参与工作的某个线程调用了Stop方法
IsComplated=false LowestBreakInteration!=null参与工作的某个线程调用了Break方法

 

五、并行语言集成查询(PLINQ)

    public class Program
    {
        [Obsolete("已经过时")]
        public static void Main(string[] args)
        {

            //AsParallel将顺序查询转换成并行查询
            //AsSequential将并行操作转换为循序操作
            var query =
                from type in Assembly.GetExecutingAssembly().GetExportedTypes().AsParallel().AsSequential().AsParallel()
                from methed in type.GetMethods()
                let obsoleteAttrType=typeof(ObsoleteAttribute)
                where Attribute.IsDefined(methed, obsoleteAttrType)
                orderby type.FullName
                let obsoleteAttrObj=(ObsoleteAttribute)Attribute.GetCustomAttribute(methed,obsoleteAttrType)
                select $"Type={type.FullName};Methed={methed.Name};Message={obsoleteAttrObj.Message}";

            //并行处理结果
            query.ForAll(Console.WriteLine);
            Console.ReadLine();
        }
    }

PLINQ处理是乱序的,如果想保持顺序,则可调用ParallelEnumerable的AsOrdered方法,调用这个方法线程会组成处理数据项,然后,这些组被合并回去,同时保持顺序(会损害性能)

PLINQ主要是划分区块,然后对区块(不同的线程)进行聚合计算,从而达到分而治之

AsParallel():将串行的代码转换为并行

AsOrdered():还原集合的初始顺序

AsOrdered和OrderBy比较

例如集合[10,1,4,2]

AsOrdered=>[10,1,4,2]

OrderBy=>[1,2,4,10]

AsSequential():将并行转换为串行

六、执行定时计算限制操作

1,构造Timer参数:

callback:回调方法

state:回调方法参数

dueTime:首次回调方法之前要等待多时毫秒(0为立即执行)

period:以后每次回调方法之前要等待多时毫秒(Timeout.Infinite线程池只调用回调方法一次)

使用Timer对象时,要确定一个变量在保持Timer对象的存活,否则对你的回调方法的调用就会停止

2,演示如何让一个线程池线程立即调用回调方法

    class Program
    {
        private static Timer _timer;
        static void Main(string[] args)
        {
            //创建但不启动计时器
            _timer = new Timer(Status, null, Timeout.Infinite, Timeout.Infinite);

            //立即启动一次计时器
            _timer.Change(0, Timeout.Infinite);

            Console.ReadLine();//防止进程终止
        }

        private static void Status(object state)
        {
            Console.WriteLine("进入Status方法");

            //这个方法由一个线程池线程执行
            Thread.Sleep(1000);//模拟其它工作(1秒)

            //返回前让Timer在2秒后再次触发一次
            _timer.Change(2000, Timeout.Infinite);
            //这个方法返回后,线程池回归池中,等待下一个工作项
        }
    }

利用Task的静态Delay方法和async和await关键字

    class Program
    {
        private static Timer _timer;
        static void Main(string[] args)
        {
            Status();
            Console.WriteLine("C");
            Console.ReadLine();//防止进程终止

            //输出:
            // A C B A B A B...
        }

        private static async void Status()
        {
            while (true)
            {
                Console.WriteLine("A");
                //在循环末尾,在不阻塞线程的前提下延迟2秒
                await Task.Delay(2000);//await 运行线程返回
                Console.WriteLine("B");
                //2秒之后,某个线程会在await之后介入并继续循环
            }
        }
    }

 

转载于:https://www.cnblogs.com/zd1994/p/7450344.html

相关文章:

  • 各大型邮箱smtp服务器及端口收集: SMTP
  • spark-2.2.0-bin-hadoop2.6和spark-1.6.1-bin-hadoop2.6发行包自带案例全面详解(java、python、r和scala)之环境准备(图文详解)...
  • qt sql多重条件查询简便方法
  • 【Git】Git常见错误
  • sequelizejs中文文档(二)Model定义
  • Spring在JSP页面使用ServletContext
  • iOS,点击button拨打电话
  • springboot使用jpa,删除功能sql报错解决
  • Hadoop技术内幕之前奏Ant
  • 对语文的新看法
  • 8.1 mnist_soft,TensorFlow构建回归模型
  • MQTT服务器搭建--Mosquitto用户名密码配置
  • Kyligence Analytics Platform Enterprise
  • 【转】VS2010/MFC编程入门之二十(常用控件:静态文本框)
  • Shiro:ajax的session超时处理
  • JavaScript-如何实现克隆(clone)函数
  • [LeetCode] Wiggle Sort
  • [数据结构]链表的实现在PHP中
  • CSS 专业技巧
  • javascript面向对象之创建对象
  • JavaScript实现分页效果
  • Mocha测试初探
  • 基于游标的分页接口实现
  • 简单基于spring的redis配置(单机和集群模式)
  • 正则表达式
  • # Java NIO(一)FileChannel
  • # Panda3d 碰撞检测系统介绍
  • #NOIP 2014# day.1 生活大爆炸版 石头剪刀布
  • #stm32整理(一)flash读写
  • #中国IT界的第一本漂流日记 传递IT正能量# 【分享得“IT漂友”勋章】
  • $().each和$.each的区别
  • (1)安装hadoop之虚拟机准备(配置IP与主机名)
  • (3)(3.2) MAVLink2数据包签名(安全)
  • (4) openssl rsa/pkey(查看私钥、从私钥中提取公钥、查看公钥)
  • (done) 两个矩阵 “相似” 是什么意思?
  • (多级缓存)多级缓存
  • (附源码)ssm旅游企业财务管理系统 毕业设计 102100
  • (免费领源码)python#django#mysql公交线路查询系统85021- 计算机毕业设计项目选题推荐
  • (太强大了) - Linux 性能监控、测试、优化工具
  • (学习日记)2024.01.19
  • (原創) 未来三学期想要修的课 (日記)
  • (转) SpringBoot:使用spring-boot-devtools进行热部署以及不生效的问题解决
  • (转)iOS字体
  • .Family_物联网
  • .NET CF命令行调试器MDbg入门(三) 进程控制
  • .NET8.0 AOT 经验分享 FreeSql/FreeRedis/FreeScheduler 均已通过测试
  • .net开发引用程序集提示没有强名称的解决办法
  • .pub是什么文件_Rust 模块和文件 - 「译」
  • /*在DataTable中更新、删除数据*/
  • @我的前任是个极品 微博分析
  • [2669]2-2 Time类的定义
  • [Angular] 笔记 6:ngStyle
  • [AutoSar]BSW_OS 01 priority ceiling protocol(PCP)
  • [CF226E]Noble Knight's Path
  • [CSS]CSS 的背景