异步方法在当前线程(主线程)启动AsyncCallback。

7

有一个TcpListener类的服务器。它使用BeginAcceptTcpClient(AsyncCallback, Object)方法接受传入连接。

这段代码是在MSDN示例中编写的。

public static ManualResetEvent tcpClientConnected = 
    new ManualResetEvent(false);

public static void DoBeginAcceptTcpClient(TcpListener 
    listener)
{
    while(true)
    {
        tcpClientConnected.Reset();
        Console.WriteLine("Waiting for a connection...");
        listener.BeginAcceptTcpClient(
            new AsyncCallback(DoAcceptTcpClientCallback), 
            listener);
        tcpClientConnected.WaitOne();
    }
}
public static void DoAcceptTcpClientCallback(IAsyncResult ar) 
{
    TcpListener listener = (TcpListener) ar.AsyncState;
    TcpClient client = listener.EndAcceptTcpClient(ar);
    Console.WriteLine("Client connected completed");
    tcpClientConnected.Set();
    while(true)
    {
         //Receiving messages from the client
    }
}

问题在于DoAcceptTcpClientCallback(IAsyncResult ar)方法有时候会在当前线程(main)中开始执行,而不是在新线程中执行,并阻塞了它(main)。因此,接下来的连接无法被接受。 请帮忙理解为什么不为此方法创建一个新的线程。

你没有向我们展示,但我想//从客户端接收消息由于某种原因正在切换回使用同步API调用。不要这样做。一旦你进入异步(以某种不涉及创建线程的方式),你就不知道将在哪个线程上运行 - 因此你应该只运行足以调度下一个异步方法并返回所需的时间。 - Damien_The_Unbeliever
首先,为什么您不使用 AcceptTcpClientAsync 呢?它在所有支持的 .NET 版本中都可用,并且可以使异步编程变得更加容易。 - Panagiotis Kanavos
第二个“异步”并不意味着“不同的线程”。它意味着您的线程不会阻塞等待响应。在Windows中,IO操作甚至可能不需要单独的线程,因为在驱动程序级别上,IO始终是异步的。该API 模拟 阻塞以使单线程编程更加容易。 - Panagiotis Kanavos
1个回答

4
是的,正如您发现的那样,不能保证您的AsyncCallback在新线程上调用。如果异步操作完成得非常快,以至于回调可以从同一线程同步运行,那么就会发生这种情况。
当旧的异步模型中的BeginXXX调用想要返回时,如果你等待的事情已经发生,例如在您的情况下是一个连接,为了防止不必要的上下文切换,它会将IAsyncResult.CompletedSynchronously设置为true并同步执行您的回调,在您的示例中无限制地阻塞了while (true)循环中的线程,永远不会让它从BeginAcceptTcpClient调用中返回。
您应该通过继续保持异步并快速返回来考虑该场景。
另外,请查看使用async/await,它们使异步编程更加容易。

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