You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#中调用base.ReadAsync为何递归调用自身而非父类Stream方法?

解决TCPStream重写ReadAsync时调用base导致栈溢出的问题

嗨,这个问题我之前踩过坑,本质就是无限递归调用搞出来的栈溢出,我给你捋清楚原因和解决办法:

为啥会栈溢出?

你在TCPStream里重写了带CancellationToken的ReadAsync,然后调用base.ReadAsync(...)——这里的base指的是NetworkStream,但大概率NetworkStream并没有自己实现这个带token的ReadAsync方法,它直接继承了Stream类的默认实现。而Stream的这个默认实现逻辑是:如果没有取消请求,就转而调用不带token的ReadAsync(byte[], int, int)重载。

如果你的TCPStream刚好也重写了不带token的ReadAsync,或者NetworkStream的不带token的ReadAsync又绕回了带token的版本,那这就形成了循环调用:TCPStream.ReadAsync(带token) → NetworkStream.ReadAsync(带token) → Stream.ReadAsync(带token) → TCPStream.ReadAsync(不带token) → TCPStream.ReadAsync(带token)……反复调用自己,栈自然就爆了。

怎么解决?

核心思路就是跳过中间的NetworkStream,直接调用最顶层Stream类的ReadAsync方法,别让调用链绕回自己的重写逻辑。你可以把当前实例强制转换成Stream类型,再调用方法,这样就会直接执行Stream的原始实现,不会触发任何子类的重写:

public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
    try
    {
        // 强制转成Stream类型,直接调用基类的ReadAsync,避开递归
        return ((Stream)this).ReadAsync(buffer, offset, count, cancellationToken);
        
        // 这里可以继续加你自己的业务逻辑,比如重试逻辑之类的
    }
    catch (Exception ex)
    {
        // 按需处理异常
        throw;
    }
}

如果你需要调用不带token的版本,也是一样的道理:

return ((Stream)this).ReadAsync(buffer, offset, count);

额外提一句

要是你不确定NetworkStream有没有自己实现某个方法,直接看它的源码或者文档就行。像带CancellationToken的ReadAsync,.NET里很多中间类都不会特意重写,都是用Stream的默认实现,这就很容易踩递归的坑。

内容的提问来源于stack exchange,提问作者Fahad Usman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 03:15:38