BeginReceive / BeginRead 超时问题

16
我正在使用NetworkStream和TcpClient通过BeginRead异步接收数据。我需要对此操作应用超时,以便在指定时间后中止读取。
据我所知,NetworkStream或TcpClient不支持这种方式-有一个ReceiveTimeout属性,但似乎只适用于同步等效的“读取”。
即使是底层的Socket类在其BeginReceive方法中也不支持超时。
我已经搜索了这个问题,并且唯一建议的解决方法是设置另一个后台线程来取消操作,如果它在超时期限内未完成。这似乎是一种可怕的方法。肯定有更好的方法吧?
2个回答

2

这是唯一的方法,因为当你使用异步操作时,启动操作的线程正在执行其他任务。同步版本提供了超时功能,因为执行线程被阻塞直到读取操作完成。

然而,如果您必须使用后台线程来取消操作,继续使用异步Begin/End方法就没有太多意义了。如果您将要启动一个后台线程,请从后台线程执行同步读取操作,然后您可以使用ReceiveTimeout。


1
Joel指出了不使用后台线程取消操作的正确方向,但如果不明显的话,许多人会使用一个线程(可能来自工作线程池)定期清理死连接。这是为了释放套接字句柄等资源,特别是在遇到拒绝服务攻击(有意或无意)时尤为重要。 - eselk
另一个选项是 System.Threading.Timer,它不会占用线程。 - hangar
@hangar,Timer 有4个实现方式,System.Threading.Timer 使用了一个线程,但它在线程池上运行,因此您无需手动管理必要的资源来使计时器函数正常工作。请参见 https://learn.microsoft.com/zh-cn/dotnet/api/system.timers.timer?view=netcore-3.1 - Kieran Devlin

1

等待ManualResetEvent并设置一些超时值,以便在任务完成时发出信号。如果在发出信号之前超时,则说明异步操作从未完成。

private ManualResetEvent receiveDone = new ManualResetEvent(false);

receiveDone.Reset();
socket.BeginReceive(...);
if(!receiveDone.WaitOne(new TimeSpan(0, 0, 0, 30))) //wait for 30 sec.
    throw new SocketException((int)SocketError.TimedOut);

在 BeginReceive 回调函数内部,使用


private void ReceiveCallBack(IAsyncResult ar)
{
    /** Use ar to check if receive is correct and complete */
    receiveDone.Set();
}

1
因为这正是我(天真地)所做的,但它不起作用,所以我会点踩。每次调用BeginReceive而没有匹配的EndReceive调用都会泄漏内核资源。在类似于Perfmon的东西中绘制进程活动并观察非分页池缓慢泄漏。如果持续时间足够长(在我的情况下,几个小时),您最终将收到一个带有错误代码WSAENOBUFS的异常;“由于系统缺乏足够的缓冲区空间或队列已满,无法执行套接字上的操作。” - Anthony
我还没有验证过,但我们是否可以在抛出异常之前调用 socket.EndReceive(..) 来解决您的问题?调用 'EndReceive所需的 IAsyncResult 是由BeginReceive` 返回的。赞同您的评论。感谢更新。 - vivek.m
所以,这并不是异步操作。 我的意思是,如果你阻塞线程直到超时,使用BeginReceive有什么意义呢?还不如使用带有套接字超时的同步操作。 - RoeeK

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接