C# 异步编程学习 十六:Value Task<T>

C# 异步编程学习 十六:Value Task

1.Value Task 用于微优化场景,您可能永远不需要编写返回此类的方法。
2. Task 和 Task 是引用类型, 实例化他们需要基于堆的内存分配和后续的搜集。
3. 优化的一种极端形式是编写无需分配此类内存的代码。 换句话说,这不会实例化任何应用类型, 不会给垃圾回收集增加负担。
4. 为了支持这种模式,C# 引入了Value Task 和Value Task 这两个struct ,编译器准许他们代替Task 和Task.
5. 如果同步完成,则await value Task 是无分配的。int answer= await foot();
6. 如果操作不是同步完成的, valueTask 实际上就会创建一个普通的Task ,并将await 转发给他。
7. 使用 AsTask方法, 可以把 ValueTask 转化为Task 也包括非泛型版本。

使用Value Task 时的注意事项

  1. ValueTask 并不常见,它的出现纯粹为了性能。
  2. 这意味着它被不恰当的值类型语义所困扰,这可能导致意外,为了避免这种情况,必须避免以下情况:
    多次await 同一个ValueTask. 操作没有结束就调用 .GetAwait().GetResult();
  3. 如果你需要这些操作,那么就应该先调用AsTask 返回操作它的Task。避免上述陷阱最简单的办法就是直接用await方法调用。 await foo();
  4. 将ValueTask 赋给变量时,就可能引发错误。 ValueTask valueTask=Foo();
  5. 将其立即转化为普通的Task 就可以避免此类错误的发生。Task task=Foo().AsTask();

避免过度弹回

  1. 对于循环中多次调用的方法,通过调用ConfigureAwait 方法,就可以避免重复的弹回到UI消息循环 带来的开销。

  2. 这强迫Task 不把continuation弹回给上下文。从而将开销消减接近上下文切换的成本,如果你用await方法同步完成,则开销会小的多。
    async void A() {。。。。await B();。。。。}

    async Task B()
    {
    for(int i=0;i<1000;i++)
    {
    await C();
    }
    }

    async Task C() {…}

    这意味着对于方法B和C, 我们取消了UI程序中的简单线程安全模型,即代码在UI线程上运行,并且只能在await 语句期间被抢占,但是,方法A不受影响,如果在一个UI线程上启动,他将保留在UI线程上
    这种优化在编写库时特别重要,您不需要简化线程安全性带来的好处,因为您的代码通常不与调用方法共享状态,也不访问UI控件。

取消
使用取消标志来实现对并发进行取消,可以封装一个类:
class CancellationToken
{
public bool IsCancellationRequested { get;private set; }
public void Cancel()
{
this.IsCancellationRequested = true;
}
public void ThrowIfCancellationRequested()
{
if (this.IsCancellationRequested)
{
throw new OperationCanceledException();
}
}

}

CancellationToken 和CancellationTokenSource 两个预定义类

  1. 先不管线程安全,应该在读写IsCancellationRequested 是进行 lock ,这个模式非常有效,CRL也提供了CancellationToken 类, 它的功能和前面的例子类似。
  2. 但是他缺少的一个Cancel方法,Cancel方法是在另外一个类上进行暴露, CancellationTokenSource。
  3. 这种 分离的设计是出于安全考虑,只能对CancellationToken 访问的方法可以检查取消,但是不能实例化取消。

Delay方法

  1. CLR里大部分的异步方法都支持Cancellationtoken, 包括Delay方法
    async Task Foo(CancellationToken token)
    {
    for (int i = 0; i < 10000; i++)
    {
    Console.WriteLine(i);
    await Task.Delay(1000, token);
    }
    }
  2. 这个时候,task 在遇到请求时会立即停止(而不是1s之后才停止)
  3. 这里边我们无需调用 ThrowIfCancellationRequested, 因为Delay 会替我们做。
  4. 取消标记在调用栈中能很好地向下传播(就像是因为异常,取消请求在调用栈中向上级联一样)。

同步方法

  1. 同步方法也支持取消(例如Task的Wait方法)。 这种情况下,取消指令需要异步发出(例如: 来自另外一个Task)
    Task.Delay(1000).ContinueWith(_ => { cancelSource.Cancel(); });

其他

  1. 事实上,您可以在构造CancellationTokenSource 时指定一个时间间隔,以便在一段时间后取消,他对实现超时是非常有用的,无论是同步还是异步。
  2. CancellationToken 这个struct提供一个Register 方法,他可以让你注册一个回调委托,这个委托会在取消时触发。 他返回一个对象,这个对象在取消注册时可以被Dispose掉。
  3. 编译器的异步函数生成的Task在遇到OperationCanceledException 异常时会自动进入取消状态(IsCanceled返回true, IsFaulted返回false)
  4. 使用Task.Run创建Task也是如此,这里是指向 构造函数传递

进度报告

  1. 有时候,你希望异步方法在运行过程中能实现一些反馈,一个简单地办法就是传入一个Action委托,当进度发生变化时候触发方法调用:
    public Task PrintNumberFoo(UInt32 maxNu, Action OperationChangedAct)
    {
    return Task.Run(() =>
    {
    for (int i = 0; i <= maxNu; i++)
    {
    if (i % 10 == 0)
    {
    OperationChangedAct(i / 10);
    }
    //do something
    Console.WriteLine(i);
    //Task.Delay(10000);
    Thread.Sleep(1000);
    }

         });
     }
    

注意:在富客户端应用中,从一个work线程中,取执行UI线程中的方法可能会导致线程安全问题。

IProgress 和Progress

  1. CLR提供一个对类型来解决此类问题。

  2. 使用Iprogress

    public Task PrintNumberFoo(UInt32 maxNU, IProgress progressAct)
    {
    return Task.Run(() =>
    {
    for (int i = 0; i <= maxNU; i++)
    {
    if (i% 10== 0||i==maxNU)
    {
    progressAct.Report(i*1.0 / maxNU);
    }
    Console.WriteLine(i);
    Thread.Sleep(10);
    }

         });
     }
    
  3. 使用 Progress 的构造函数可以接受一个Action 类型的委托
    ClassLibrary.Mytext mytext = new ClassLibrary.Mytext();

    Progress<double> progressAct = new Progress<double>(ret => {
             textBox1.AppendText(string.Format("已经完成:{0} %", ret*100));
             textBox1.AppendText(Environment.NewLine);
             });
    
         mytext.PrintNumberFoo(845, progressAct);
    

4.Process 还有一个ProcessChanged 事件,您可以订阅他。而不是【或者附加】将Action委托传递给构造函数。
5. 在实例化 Process 时,类捕获同步上下文(如果存在 ) 。 当foo调用Report 时, 委托通过上下文调用。
6. 异步方法可以通过将int替换为公开一系列属性的自定义类型来实现更精细的进度报告。

拓展Tap 了解 Task组合器

  1. .net core 暴露了很多返回Task且可以await方法。
  2. CRL提供了两个Task组合器
    Task.WhenAny 和 Task.WhenAll

两个功能相同的方法
public async Task GetTotalSize(string[] uris)
{
IEnumerable<Task<byte[]>> downloadTasks = uris.Select(uri => new WebClient().DownloadDataTaskAsync(uri));
byte[][] contents = await Task.WhenAll(downloadTasks);
return contents.Sum(c => c.Length);
}

    public async Task<int> GetTotalSizeTwo(string[] uris)
    {
        IEnumerable< Task<int>> downLoadTasks = uris.Select(async uri =>
        {
           return  (await new WebClient().DownloadDataTaskAsync(uri)).Length;
        });

        int[] contextLengths = await Task.WhenAll(downLoadTasks);
        return contextLengths.Sum();
    }
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

望天hous

你的鼓励是我最大动力~谢谢啦!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值