首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >是否需要在异步BeginReceive回调中同步TCP/UDP客户端?

是否需要在异步BeginReceive回调中同步TCP/UDP客户端?
EN

Stack Overflow用户
提问于 2015-10-14 00:01:14
回答 1查看 1.2K关注 0票数 2

我有一个多线程网络应用程序,它使用UdpClientTcpClientTcpListener,并使用BeginReceive() EndReceive()回调模式处理接收到的连接和接收的数据。

以UdpClient为例,在这种模式中,我使用的一般工作流程是:

  1. 调用UdpClient.BeginReceive()
  2. 当接收到数据报时,执行接收回调。
  3. 打电话给UdpClient.EndReceive()收集数据报。
  4. 再次打电话给UdpClient.BeginReceive(),准备接收另一个数据报。
  5. 处理在(3)处收到的数据报。
  6. 当收到更多的数据报时重复2-5

Q:由于只有一个UdpClient对象,并且由于在下一个BeginReceive()之前总是调用EndReceive()的模式,是否有必要为这些调用锁定/同步对UdpClient对象的访问?

在我看来,另一个线程不可能干预这个工作流或使那些调用非原子化。TcpClient.BeginReceive()TcpListener.BeginAcceptTcpClient()的模式非常相似。

奖励Q:单个UdpClient对象是否需要声明为static (如果需要staticobject )?

注意:我是而不是,询问是否需要在数据报处理期间执行任何锁定。只涉及此模式和UdpClient TcpClient TcpListener对象。

编辑

作为澄清,(忽略异常处理)是这样的代码:

代码语言:javascript
复制
private void InitUDP()
{
    udpclient = new UdpClient(new IPEndPoint(IPAddress.Any, Settings.Port));

    udpclient.BeginReceive(new AsyncCallback(receiveCallback), udpclient);
}

private void receiveCallback(IAsyncResult ar)
{
    UdpClient client = (UdpClient)ar.AsyncState;

    IPEndPoint ep = new IPEndPoint(IPAddress.Any, 0);

    byte[] datagram = client.EndReceive(ar, ref ep);

    udpclient.BeginReceive(new AsyncCallback(receiveCallback), udpclient);

    processDatagram();
}

实际上与此代码不同或保护程度较低:

代码语言:javascript
复制
private void InitUDP()
{
    udpclient = new UdpClient(new IPEndPoint(IPAddress.Any, Settings.Port));

    udpclient.BeginReceive(new AsyncCallback(receiveCallback), udpclient);
}

private void receiveCallback(IAsyncResult ar)
{
    UdpClient client = (UdpClient)ar.AsyncState;

    IPEndPoint ep = new IPEndPoint(IPAddress.Any, 0);

    lock(_lock)
    {
        byte[] datagram = client.EndReceive(ar, ref ep);

        udpclient.BeginReceive(new AsyncCallback(receiveCallback), udpclient);
    }

    processDatagram();
}
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2015-10-14 04:59:51

是否有必要为这些调用锁定/同步对UdpClient对象的访问?

不,不完全是,但也许不是因为你可能认为的原因。

如果在处理当前数据报之前调用BeginReceiveFrom() (或仅调用BeginReceive() ),则实际上可以同时调用相同的回调。这是否真的会发生取决于许多事情,包括线程调度、线程池中当前可用的IOCP线程数量,当然还有是否有数据报可接收。

因此,您肯定存在这样的风险:在处理当前数据报之前,将发生新数据报的接收,并且在完成第一个数据报的处理之前将开始对该数据报的处理。

现在,如果数据报的处理涉及对其他共享数据的访问,那么您肯定需要围绕其他共享数据进行同步,以确保对其他数据的安全访问。

但就数据报本身而言,网络对象是线程安全的,因为您不会同时使用…损坏对象。确保你以连贯的方式使用它们仍然取决于你。但是对于UDP协议来说,这比TCP更容易。

UDP不可靠。它缺乏三个非常重要的保障:

  1. 没有任何保证数据报将被交付。
  2. 不能保证数据报不超过一次。
  3. 不能保证相对于发送数据报的其他数据报,数据报将以相同的顺序交付。

最后一点在这里特别重要。您的代码已经能够处理无序的数据报处理。因此,无论是由于网络本身,还是由于您在处理当前数据报之前启动了一个新的I/O操作,如果您的代码编写正确,那么您的代码将成功地处理它。

有了TCP,情况就不同了。如果您已经启动了I/O操作,那么在处理当前的I/O操作之前,它肯定会完成。但是与UDP不同的是,您确实对TCP有一些保证,包括在套接字上接收的数据将按照发送数据的相同顺序接收。

因此,只要您在完全处理当前已完成的I/O操作之前不调用BeginReceive(),一切都会好起来的。您的代码以正确的顺序查看数据。但是,如果您之前调用了BeginReceive(),那么您的当前线程在完成当前I/O操作之前可能会被抢占,而另一个线程可能最终处理一个新完成的I/O操作。

除非您已经对接收到的数据进行了某种同步或排序,以便考虑到处理I/O完成不正常的可能性,否则这将破坏您的数据。不太好。

有充分的理由同时执行多个接收操作。但是,它们通常与对高可伸缩性服务器的需求有关。还存在与发出多个并发接收操作相关的负面因素,包括确保按正确顺序处理数据所增加的复杂性,以及在堆中有多个固定/固定缓冲区的开销(尽管这可以通过多种方式减轻,例如分配足够大的缓冲区以确保它们在大对象堆中)。

我将避免以这种方式实现代码,除非您必须解决特定的性能问题。即使在处理UDP时,尤其是在处理TCP时。如果您确实以这种方式实现代码,请非常小心地执行它。

单个UdpClient对象是否需要声明为静态对象(如果需要静态锁定对象,则需要静态锁定对象)?

存储对UdpClient对象的引用的位置一点也不重要。如果您的代码需要一次维护多个UdpClient,那么在单个UdpClient-type字段中存储引用甚至不太方便。

static所做的一切就是改变访问该成员的方式。如果不是static,则需要指定找到成员的实例引用;如果是static,则只需指定类型。就这样。它与线程安全本身一点关系都没有。

最后,对于两个代码示例,它们在功能上是等价的。不需要保护对EndReceive()BeginReceive()的调用,而且您的lock不包含这些方法的任何其他部分(例如,数据报的实际处理),因此它没有真正完成任何事情(除了可能增加上下文交换机的开销)。

在并发场景中,第一个线程可能在离开lock之前被抢占,但在调用BeginReceive()之后。这可能导致第二个线程被唤醒,以便在第二个I/O完成过程中处理回调。第二个线程将命中lock并停止运行,允许第一个线程继续执行并离开lock。但是同步所做的一切只是让事情慢下来。它不阻止对数据报数据本身的任何并发访问,这是(可能的)重要部分。

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/33114541

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档