C#并发模型

并发含义

并发在程序的实现中,需要格外的下功夫,是开发可靠性和可拓展性程序的利器。

主流上对并发的理解是:不阻塞主线程/能够同时处理多件事情,这两种说法其实是相通的,但又不是很好理解。

不阻塞主线程,其实这里的主线程,是调用线程,也就是执行代码的线程,我们要将其理解为上下文。不阻塞的模式是有多种实现方法的,并发可算其中一种。

同时处理多件事情,这里指的也是上下文,其实也暗指不阻塞上下文,让其能够及时释放,处理其他事情,当然也可理解为多线程环境。

另外需要提出来的一点是,多线程算并发的一种实现,但是本文主要介绍并发的模式,模式是统一抽象的。

C#并发模型发展路线

  • APM

.NET中的异步模型,一开始是将处理和完成分开的,就是BeginEnd的实现,通过IAsyncResult进行数据传递,这增加了异步代码开发的复杂性,也降低了程序的可读性,所以应用起来是比较麻烦的,典型的API如下:

  • EAP

EAP是基于委托和事件实现异步,也是将处理和完成分开,具体引用的方法就是BeginInvokeEndInvoke,使用IAsyncResult进行数据传递,同样具有APM的问题。

  • TPL

TPL是使用async/await关键字的引入,将处理和完成结合在一起,简化了程序的开发,极大的提升了开发效率。

其实并发模式并不会提升单个程序代码的执行速度,其是通过资源优化来提升效率的,何为资源优化,就需要了解CLR运行的环境。

CLR线程池中,是区分工作线程和I/O线程的,工作线程负责执行调度,I/O线程负责进行IO处理,所以并发资源优化就是在程序调度中(执行代码),发生IO的时候(大多数都是IO操作),将调度程序进行及时释放,而不是同步等待,让其能够处理其他程序,而IO程序进行处理IO,当IO完成后,IO线程通知调度程序(可能是UI或者是线程池等),接着处理下面程序,这样就释放了工作线程,也就是不阻塞工作线程,从而提升了执行效率。

并发模式就是如此设计的,下面主要以async/await来介绍上下文的运行,async/await的调度有如下几个原则:

  1. 第一个await及以上的程序都是调用线程执行的,不论在什么情况下
  2. 区分UI/Request线程的执行,当存在多个await的时候,第二个及以后的await代码块在UI/Request的情况下,不进行配置的话,都是UI/Request调度的,在线程池的情况下,则由线程池提供调度程序

那么在UI/Request线程的情况下,如何配置才能让第二个及之后的await代码块使用线程池调用呢,就是对Task.ConfigureAwait(false)进行配置。

C#并发

1. 基于任务的异步模型

基于Taskasync/await就是该异步模型

2. 并行

并行也是并发的一种模式,在不同的场景下,要有选择的使用并发的不同技术栈,这样才能最大化效率。密集I/0的情况下,async/await很适合,密集计算的情况下,并行则很适合。

  • 数据并行 Parallel PLINQ

数据并行是针对数据的一种并发模式,在C#中,就针对集合,有ParallelForForEach,有PLINQ

Parallel并行是.NET的封装,通过充分的利用多核的计算能力,提升效率,需要注意的是,这会导致无序,所以需要考虑清楚。

  1. 数据可以独立处理,无状态的场景
    var source = Enumerable.Range(0, 10000);
     Parallel.ForEach(source, item => { Console.WriteLine($"输出:{item.ToString()}"); });

  2. 数据有状态,需要规约的场景(PLINQ可能更适合),这里需要注意处理结果的锁(处理具有幺半群的性质则不需要)

    var source = Enumerable.Range(0, 100);
     Parallel.ForEach<int, long>(source, () => 0L, (item, state, acc) => acc + item, result =>
     {
         Console.WriteLine($"运算结果为:{result.ToString()}"); // 注意共享状态
    });

     

  3. PLINQ

    PLINQ得益于LINQ的支持,可以在聚合的场景下有很好的作用,尤其是Aggregate对规约的支持。

  var source = Enumerable.Range(0, 100);
  var result = source.AsParallel().Sum(); // 聚合
 Console.WriteLine($"输出:{result.ToString()}");

 

             

    PLINQ下的FPReduce规约

 

public static T Reduce<T>(this ParallelQuery<T> query, Func<T, T, T> func)
        {
            return query.Aggregate((acc, item) => func(acc, item));
        }

        public static T Reduce<T>(this ParallelQuery<T> query, T seed, Func<T, T, T> func)
        {
            return query.Aggregate(seed, (acc, item) => func(acc, item));
        }

        public static TSeed Reduce<TSeed, TMessage>(this ParallelQuery<TMessage> query, TSeed seed, Func<TSeed, TMessage, TSeed> func)
        {
            return query.Aggregate(seed, (acc, item) => func(acc, item));
    }

 

  • 任务并行 Parallel

任务并行是指同时执行多个独立的方法,这些方法应该是互不影响的。

Parallel.Invoke(() => { Console.WriteLine($"第一个方法"); }, () => { Console.WriteLine($"第二个方法"); }, () => { Console.WriteLine($"第三个方法"); });

  

  • 异常处理

Parallel任务并行的时候,会将异常统一捕获AggregateException,(.NET中没有Supress的异常处理)

 

            try
            {
                Parallel.Invoke(() => { throw new TimeoutException("超时异常"); }, () => { throw new ArgumentException("参数异常"); }, () => { Console.WriteLine($"第三个方法"); });
            }
            catch (AggregateException ex)
            {
                ex.Handle(exc =>
                {
                    Console.WriteLine($"异常类型:{exc.GetType()},异常内容:{exc.Message}");
                    return true;
                });
            }
            catch (Exception ex)
            {
            }

 

3. 数据结构

 

  • 不变数据结构

类库名称: System.Collections.Immutable

数据的不变性是解决并发过程中数据共享的完美方法,常用的不变数据结构包括:

ImmutableQueue -- 不可变队列

ImmutableStack -- 不可变栈

ImmutableList -- 不可变列表

ImmutableHashSet -- 不可变set

ImmutableDictionary -- 不可变字典

ImmutableDictionary -- 不可变排序字典

 

代码实例如下:

数据的不变性是解决并发过程中数据共享的完美方法,常用的不变数据结构包括:

ImmutableQueue -- 不可变队列

ImmutableStack -- 不可变栈

ImmutableList -- 不可变列表

ImmutableHashSet -- 不可变set

ImmutableDictionary -- 不可变字典

ImmutableDictionary -- 不可变排序字典

  • 线程安全数据结构

类库名称: System.Collections.Concurrent

线程安全的数据结构主要应用CASCompare And Swap)算法,进行数据的类似乐观锁的同步操作。常用的线程安全数据结构如下:

ConcurrentDictionary -- 线程安全的字典

ConcurrentQueue -- 线程安全的队列

ConcurrentStack -- 线程安全的栈

ConcurrentBag -- 线程安全的背包

  • 阻塞类数据结构

类库:System.Collections.Concurrent

阻塞类的数据结构是天然的生产者/消费者模式,提供区别于其他数据结构的操作,如:停止,这里总结的模式是:线程轮询的情况下,使用异常退出循环而不是标志位。

常用的阻塞类数据结构如下:

BlockingCollection -- 阻塞队列

BlockStack -- 阻塞栈

BlockBag -- 阻塞背包

4. 取消

取消是对系统可靠性的一种体现,在基于任务的开发过程中,取消的方法必须考虑,.NET中提供了CancellationTokenSource -> CancellationToken类进行取消,注意,该类对任务的取消是协助式的,也就是说,在轮询等语境中,必须手动添加取消的程序,这里需要注意的是,CancellationToken有两种取消的方法:

  1. 判断标志位 cts.Token.IsCancellationRequested;
  2. 抛出异常进行取消 cts.Token.ThrowIfCancellationRequested();引发 System.OperationCanceledException

建议使用抛出异常的方式,很多类库采用的便是引发异常的做法来进行停止。

下面介绍下CancellationTokenSource 的其他几种方法:

  1. 注册回调

CancellationTokenSource 可以在取消触发(调用Cancel()方法)的时候,回调一部分程序Action,具体用法如下:

 

var cts = new CancellationTokenSource();
cts.Token.Register(() => { Console.WriteLine($"取消的时候进行回调"); });

  2. 链式的取消标志

CancellationTokenSource 可以进行组合,当取消其时,将其他组合的CancellationTokenSource 也进行取消,具体用法如下:

 

 var cts = new CancellationTokenSource();
 var cts2 = new CancellationTokenSource();
var cts3 = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, cts2.Token);

 

  3.延时取消

CancellationTokenSource 可以配置进行延时取消,具体用法如下:

 

var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(2)); // 2s后取消

 

5. 异常处理

基于任务的异步模式,由于.NET的出色处理,可以直接用try catch进行异常捕获,但是有几种场景下,需要注意:

  1. 任务并行Paralle.Invoke
  2. Task.WhenAll()Task.WhenAny(),Task.WaitAll(),Task.WaitAny();

上述情境中,由于执行体是多个任务或方法,所以异常是AggregateException。 在命令式的异常处理中,由于模板统一,所以会增加多的副作用,所以在FP中,鼓励使用SuccessFault的状态进行异常处理,而且这样做可以强制开发进行处理,将在下面的FP模块中介绍到OptionResult模式。Result模式就是Hsl使用的。

另外,Task.When Task.Wait是有明显区别的,When在异步的语境中使用,Wait在同步的语境中使用,一定注意。

 

6. 锁和CAS(Atom原子处理)

 

本文涉及到异步模式,所以不过多的介绍线程同步的技术,可以参考《多线程》一文,这里将要介绍的是CAS的原子处理。

 

原子处理可以替代锁,类似于乐观锁的实现,主要应用于多线程共享数据的场景下,应用的就是.NET提供的Interlocked类。

 

场景一: 数字的原子操作

 

var res = 10;
    Interlocked.Increment(ref res); // 原子加1
    Interlocked.Decrement(ref res); // 原子减1
Interlocked.Add(ref res, 10); // 任意加

 

场景二: 自定义原子操作类,用于多线程数据共享

由此可知,在多线程的数据共享中(当然,这样的情况要避免),可使用Interlocked进行原子处理,下面将自定义的AtomUtil代码示例:

 

 public sealed class AtomUtil<T> where T : class
    {
        #region 构造函数

        public AtomUtil(T value)
        {
            this.value = value;
        }

        #endregion 构造函数

        #region 变量

        private volatile T value; // 实例的当前值
        public T Value => value;

        #endregion 变量

        #region 方法

        // 赋值方法 -- CAS算法
        public T Swap(Func<T, T> factory)
        {
            T original, temp;
            do
            {
                original = value;
                temp = factory(original);
            } while (Interlocked.CompareExchange(ref value, temp, original) != original);
            return original;
        }

        #endregion 方法
    }

 

模式总结

1. TDF

TDFTPL DataFlow的简称,使用类库为:Microsoft.Tpl.Dataflow

TDF用来处理数据流,块可分为源块、转换块、目标块,源块就是负责生产数据,转换块用于数据处理,同FP中的Map/Bind类似,目标快用来处理数据。

TDF可以实现参与者模式,同F#MailBoxProcesser类似的代理,每个块由消息缓存、状态、处理输出构成。

  • 基础块

常用的块有BufferBlockTransformBlockActionBlock

BufferBlock负责缓存数据,可以起到节流的作用(通过配置DataflowBlockOptionsBoundedCapacity)

TransformBlock用于数据的转换处理。

ActionBlock用于执行代码。

上述三种块的应用在代理(参与者模式)中体现。

块的连接执行和DDD中的CQRS是有相同优势的,可以帮助对业务流程进行梳理(DDD中的ES还可以实现事件溯源)

  • 块的取消

块的取消分为两个步骤:

  1. LinkTo设置PropagateCompletion = true,用于传递异常和取消
  2. Complete(),等待取消

实例代码如下:

// 1. 结束第一步,配置结束传递  
var options = new DataflowLinkOptions()  
{  
    PropagateCompletion true // 传递异常和结束  
};  
  
// 2. 块组合  
BufferBlock.LinkTo(TransfromBlock, options);  
TransfromBlock.LinkTo(ActionBlock, options);  
  
// 3. 结束  
BufferBlock.Complete(); // 结束  
await BufferBlock.Completion.ContinueWith(t => TransfromBlock.Complete());  
await TransfromBlock.Completion.ContinueWith(t => ActionBlock.Complete());  
await ActionBlock.Completion;  
  • 块的异常处理
  • 代理

代理是参与者模式的实现,轻量级的生产消费,可以用于解耦业务处理。

代理可分为无状态代理(只处理),有状态处理(规约)和双向通信代理,具体实现如下:

 

 public sealed class StatefulDataFlowAgent<TState, TMessage> : IAgent<TMessage>
    {
        #region 构造函数

        public StatefulDataFlowAgent(TState initial, Func<TState, TMessage, Task<TState>> action, CancellationTokenSource Cts)
        {
            AccState = initial;
            var options = new ExecutionDataflowBlockOptions()
            {
                CancellationToken = Cts == null ? CancellationToken.None : Cts.Token,
            };

            ActionBlock = new ActionBlock<TMessage>(async (data) => AccState = await action(AccState, data), options);
        }

        #endregion 构造函数

        #region 变量

        public TState AccState; // 累加状态
        private ActionBlock<TMessage> ActionBlock; // 执行体

        #endregion 变量

        #region 方法

        // 同步添加数据
        public void Send(TMessage data)
        {
            ActionBlock.Post(data);
        }

        // 异步添加数据
        public Task SendAsync(TMessage data)
        {
            return ActionBlock.SendAsync(data);
        }

        #endregion 方法
    }

 

 public sealed class StatefulReplayDataFlowAgent<TState, TMessage, TReplay> : IReplayAgent<TMessage, TReplay>
    {
        #region 构造函数

        public StatefulReplayDataFlowAgent(TState initial, Func<TState, TMessage, Task<TState>> projection,
            Func<TState, TMessage, Task<Tuple<TState, TReplay>>> ask, CancellationTokenSource Cts)
        {
            AccState = initial;
            var options = new ExecutionDataflowBlockOptions()
            {
                CancellationToken = Cts == null ? CancellationToken.None : Cts.Token,
            };

            ActionBlock = new ActionBlock<Tuple<TMessage, TaskCompletionSource<TReplay>>>(
                async (data) =>
                {
                    if (data.Item2 == null)
                        AccState = await projection(AccState, data.Item1);
                    else
                    {
                        var result = await ask(AccState, data.Item1);
                        AccState = result.Item1;
                        data.Item2.SetResult(result.Item2);
                    }
                }
                , options
            );
        }

        #endregion 构造函数

        #region 变量

        public TState AccState; // 累加状态
        private ActionBlock<Tuple<TMessage, TaskCompletionSource<TReplay>>> ActionBlock; // 执行体

        #endregion 变量

        #region 方法

        // 同步添加数据
        public void Send(TMessage data)
        {
            ActionBlock.Post(Tuple.Create<TMessage, TaskCompletionSource<TReplay>>(data, null));
        }

        // 异步添加数据
        public Task SendAsync(TMessage data)
        {
            return ActionBlock.SendAsync(Tuple.Create<TMessage, TaskCompletionSource<TReplay>>(data, null));
        }

        // 同步通讯
        public TReplay Ask(TMessage data)
        {
            var cts = new TaskCompletionSource<TReplay>();
            ActionBlock.Post(Tuple.Create(data, cts));
            return cts.Task.Result;
        }

        // 异步通讯
        public Task<TReplay> AskAsync(TMessage data)
        {
            var cts = new TaskCompletionSource<TReplay>();
            ActionBlock.Post(Tuple.Create(data, cts));
            return cts.Task;
        }

        #endregion 方法
    }
 public sealed class StatelessDataFlowAgent<T> : IAgent<T>
    {
        #region 构造函数

        public StatelessDataFlowAgent(Func<T, Task> action, CancellationTokenSource Cts)
        {
            var options = new ExecutionDataflowBlockOptions()
            {
                CancellationToken = Cts == null ? CancellationToken.None : Cts.Token,
            };

            ActionBlock = new ActionBlock<T>(async (data) => await action(data), options);
        }

        #endregion 构造函数

        #region 变量

        private ActionBlock<T> ActionBlock;

        #endregion 变量

        #region 方法

        // 同步添加数据
        public void Send(T data)
        {
            ActionBlock.Post(data);
        }

        // 异步添加数据
        public Task SendAsync(T data)
        {
            return ActionBlock.SendAsync(data);
        }

        #endregion 方法
    }

2. Rx

Rx使用类库:System.Reactive

RxReactive,主要用来处理连续的数据流,用推式的方法取代拉式,具有函数式的编程风格,目前总结下有两个方面可以应用。

  1. UI通知,通过节流实现UI的通知,防止UI线程假死
  2. 做观察者模式Subject

Rx使用.NETIObservableIObserver接口实现,IObservable是观察者,发布数据,IObserver是订阅者,订阅接收。

这里涉及到.NET中的IEnumrableIQueryable、和IObservable(Linq to Events),有兴趣可以自己了解。

  • Rx的转换

    Rx可以和Event,TPL等进行转换。

    // Rx注册  
    var changedObservable = Observable.FromEventPattern<FileSystemEventArgs>(FileWatcher, "Changed");  
    var createdObservable = Observable.FromEventPattern<FileSystemEventArgs>(FileWatcher, "Created");  
    var deletedObservable = Observable.FromEventPattern<FileSystemEventArgs>(FileWatcher, "Deleted");  
  • Rx处理

  缓存、窗口、节流、超时、采样、....

 

Rx 的处理能力非常强大,其API非常丰富,需要不断琢磨System.Reactive.Linq

 

1. 函数式编程FP

 

  • 纯函数

 

纯函数是指输出仅有输入决定的函数,其没有副作用。

 

  • 副作用

 

  1. 产生异常
  2. I/O
  3. 引用全局变量(状态)

 

  • 异常处理

 

FP中,异常属于副作用,通过状态的传递来进行异常处理,也就是函数的处理有成功和失败,对其状态进行判断进行处理,Option包括成功和NoneResult包括成功和失败,Result模式更加全面,具体实现如下:

 

 public class Result<T>
    {
        #region 构造函数

        // 正常情况
        public Result(T ok)
        {
            this.Ok = ok;
            Error = default(Exception);
        }

        // 异常情况
        public Result(Exception ex)
        {
            this.Error = ex;
            this.Ok = default(T);
        }

        #endregion 构造函数

        #region 变量

        public T Ok { get; } // 正常返回结果
        public Exception Error { get; } // 异常返回结果
        public bool IsFailed { get { return Error != null; } } // 结果是否为异常
        public bool IsOk { get { return !IsFailed; } } // 结果是否为正常

        #endregion 变量

        #region 方法

        // 结果处理
        public R Match<R>(Func<T, R> okMap, Func<Exception, R> failureMap)
        {
            return IsOk ? okMap(Ok) : failureMap(Error);
        }

        // 结果处理
        public void Match(Action<T> okMap, Action<Exception> failureMap)
        {
            if (IsOk)
                okMap?.Invoke(Ok);
            else
                failureMap?.Invoke(Error);
        }

        #region 隐式转换

        // 正常结果
        public static implicit operator Result<T>(T ok) => new Result<T>(ok);

        // 异常结果
        public static implicit operator Result<T>(Exception ex) => new Result<T>(ex);

        #endregion 隐式转换

        #endregion 方法
    }

 

FP中也可以使用函数来进行异常处理,Catch的函数如下,其可以处理异常,并且不产生副作用:

 

public static Task<T> Catch<T, TError>(this Task<T> task, Func<TError, T> errorHandle) where TError : Exception
        {
            var tcs = new TaskCompletionSource<T>();
            task.ContinueWith(innerTask =>
            {
                if (innerTask.IsFaulted && innerTask?.Exception?.InnerException is TError)
                    tcs.SetResult(errorHandle((TError)innerTask.Exception.InnerException));
                else if (innerTask.IsCanceled)
                    tcs.SetCanceled();
                else if (innerTask.IsFaulted)
                    tcs.SetException(innerTask?.Exception?.InnerException ?? new InvalidOperationException());
                else
                    tcs.SetResult(task.Result);
            });
            return tcs.Task;
        }

 

  • 函数组合

幺半群、单子、函子属于范畴论。

幺半群要求函数具有关联性和可交换性,且输入输出是同一种类型,如+*,在并发下顺序不影响结果。

单子包含ReturnBindReturn用于提升,Bind用于转换,也就是函数进行组合。

函子用于解构函数,将多参数的函数解构为单个参数的函数,也用于函数组合。

  • 分而治之(Fork/Join)

Fork/Join很类似与MapReduce,对数据进行转换、规约,可做节流、处理,用于IO密集型操作,代码如下:

 

using PL.Util.FP.Agent;
using PL.Util.FP.Agent.Service;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;

namespace PL.Util.FP
{

    public static class ForkJoinUtil
    {
        /// <summary>
        /// Iemnurable拓展Fork/Join模式
        /// </summary>
        /// <returns></returns>
        public static async Task<R> ForkJoin<T1, T2, R>(this IEnumerable<T1> source,
            Func<T1, Task<T2>> map,
            Func<R, T2, Task<R>> aggregate, R initianState,
            CancellationTokenSource cts, int paritionLevel = 8, int boundCapacity = 20)
        {
            cts = cts ?? new CancellationTokenSource();

            // 数据缓存添加
            var inputBuffer = new BufferBlock<T1>(new DataflowBlockOptions()
            {
                CancellationToken = cts.Token,
                BoundedCapacity = boundCapacity,
            });

            // map转换
            var blockOptions = new ExecutionDataflowBlockOptions()
            {
                CancellationToken = cts.Token,
                BoundedCapacity = boundCapacity,
                MaxDegreeOfParallelism = paritionLevel
            };
            var mapperBlock = new TransformBlock<T1, T2>(map, blockOptions);

            // 组合
            inputBuffer.LinkTo(mapperBlock, new DataflowLinkOptions()
            {
                PropagateCompletion = true,
            });

            // reduce代理
            var reduceAgent = AgentUtil.Start(initianState, aggregate, cts);

            // TDF转换为Rx,代理数据处理
            IDisposable disposable = mapperBlock.AsObservable().Subscribe(async item => await reduceAgent.SendAsync(item));

            // 数据添加
            foreach (var item in source)
            {
                // Send允许延迟,Post会丢弃数据
                await inputBuffer.SendAsync(item);
            }

            // 数据处理完成
            inputBuffer.Complete();
            var tcs = new TaskCompletionSource<R>();
            await inputBuffer.Completion.ContinueWith(t => mapperBlock.Complete());
            await mapperBlock.Completion.ContinueWith(t =>
            {
                var agent = reduceAgent as StatefulDataFlowAgent<R, T2>;
                disposable.Dispose();
                tcs.SetResult(agent.AccState);
            });

            // 返回规约数据
            return await tcs.Task;
        }
    }
}

 

  • 对象池

借助TDF实现异步池化的概念,代码如下:

 public class ObjectPoolAsyncUtil<T> : IDisposable
    {
        #region 构造函数

        public ObjectPoolAsyncUtil(int initialCount, Func<T> factory, CancellationToken cts, int timeOut = 0)
        {
            this.TimeOut = timeOut;
            this.BufferBlock = new BufferBlock<T>(new DataflowBlockOptions() { CancellationToken = cts });
            this.Factory = () => factory();

            for (int i = 0; i < initialCount; i++)
                this.BufferBlock.Post(this.Factory());
        }

        #endregion 构造函数

        #region 变量

        private BufferBlock<T> BufferBlock; // 维持对象生命周期
        private Func<T> Factory; // 获取对象方法
        private int TimeOut; // 获取对象超时时间

        #endregion 变量

        #region 方法

        #region 归还实例

        public Task<bool> PutAsync(T t)
        {
            return this.BufferBlock.SendAsync(t);
        }

        #endregion 归还实例

        #region 获取实例

        public Task<T> GetAsync()
        {
            var tcs = new TaskCompletionSource<T>();

            // CPS延续性风格,非阻塞的程序编写模式
            BufferBlock.ReceiveAsync(TimeSpan.FromSeconds(TimeOut)).ContinueWith(t =>
            {
                if (t.IsFaulted)
                {
                    if (t.Exception.InnerException is TimeoutException)
                        tcs.SetResult(this.Factory());
                    else
                        tcs.SetException(t.Exception);
                }
                else if (t.IsCanceled)
                    tcs.SetCanceled();
                else
                    tcs.SetResult(t.Result);
            });

            return tcs.Task;
        }

        #endregion 获取实例

        #region 销毁

        public void Dispose()
        {
            this.BufferBlock.Complete();
        }

        #endregion 销毁

        #endregion 方法
    }
  • 延续性CPS

延续性实现完整的并发非阻塞模型。

利用ContinueWithTaskCompletionSource

 

1. 生产者消费者 Channel

 

使用类库:System.Threading.Channels

可以实现异步的有界队列和无界队列,具体代码可自己研究

问题处理

  1. UI/Requst情况下同步语义的禁用

UI/Requset的情境下,应用Task的时候,禁止使用同步语义,如:

Task.Wait()Task.WaitAll()Task.Result等,这会导致假死。

  2.async void的禁用

async void禁止使用,这会导致无法进行异常处理。

 

posted @ 2022-01-25 17:56  慢慢zero  阅读(200)  评论(0)    收藏  举报