C#中的ValueTask和Task

前置知识背景在C#中的async/await一文中有介绍。现在讨论一下ValueTask和Task的区别和使用方法,本文是对于Stephen Toub写的文章Understanding the Whys, Whats, and Whens of ValueTask的读后总结。

ValueTask常用的使用场景

首先我们应该都知道,Task是一个class,ValueTask是一个struct。首先但就引用类型和值类型的区别,我们知道值类型是分配在栈上的,引用类型是分配在堆上的,由GC回收,所以通常来说能用值类型就用值类型,内存使用效率更高。基于这一点,我们可以看下面这个ByteStream类的ReadByteAsync()方法。


public sealed class ByteStream : IDisposable
{
    private readonly Stream stream;
    private readonly byte[] buffer;
    private int position;
    private int bufferedBytes;
    public ByteStream(Stream stream)
    {
        this.stream = stream;
        buffer = new byte[1024 * 8];
    }
    /// <summary>
    /// 由于大多数情况都是直接返回,不会执行await,所以用ValueTask这个值类型会更好
    /// </summary>
    /// <returns></returns>
    public async ValueTask<byte?> ReadByteAsync()
    {
        if(position == bufferedBytes)
        {
            position = 0;
            bufferedBytes = await stream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
        }
        if(bufferedBytes== 0)
        {
            return null;
        }
        return buffer[position++];
    }
    public void Dispose() { }
}

ValueTask <TResult>何时不得使用

The IValueTaskSource / IValueTaskSource<TResult> implementation need not support 
blocking until the operation completes, and likely doesn’t, so such an operation is
inherently a race condition and is unlikely to behave the way the caller intends. 

这里的IValueTaskSource/IValueTaskSource<TResult>是用于实现ValueTask池化的接口,当await Task时,Task未完成,此时会在堆中分配一个Task来执行continuation续延,但是没人知道这个Task完成操作后是否还可以被重用,因为有可能之前的调用方还会对该Task做一些操作。如果可以被重用的话,那么异步架构会缓存这个对象,操作未完成的时候不可以在多个进行中的异步操作中使用同一个对象,可以在操作完成之后重用。对于ValueTask的重用对象机制,就有IValueTaskSource这个接口。所以这就是为什么不能再操作未完成时使用GetAwaiter().GetResult(),其实是跟IValueTaskSource有关系;

ValueTask一定比Task性能更好吗?

是的,但是使用方法限制较多,还不能并发使用,如果对自己的掌握程度有自信,可以一直使用ValueTask。有如下情况你应该使用ValueTask:
a) 你期望你的API的消费者直接await它们,比await Task更好的一点是,ValueTask是值类型,值类型会快速回收,不像引用类型可能呆在GC里很长时间;
b) 如果你的API对内存开销很敏感,使用ValueTask;
c) 你要么期望同步完成是非常普遍的情况,此时await直接返回,要么你能够有效地为异步完成使用对象池,因为Task的池化是异步架构自己做的,ValueTask的重用需要自己处理,除非你用IValueTaskSource接口。这一点是比较难的,你要知道如何在异步完成的时候在对象池中重用对象。

总结

这里只是简单讲了ValueTask的使用方法,错误用法,更深层的原因得看IL源码了,这个暂时搁置,只知道它不能多次await、不能并发await就够用了。.GetAwaiter().GetResult()这个部分就暂时别管。

  • 32
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值