C#中的异步递归是否安全?(使用异步ctp/.net 4.5)

21

在C#中使用async ctp或vs.net 2011 beta,我们可以像这样编写递归代码:

public async void AwaitSocket()
{
    var socket = await this.AcceptSocketAsync(); //await socket and >>return<< to caller
    AwaitSocket(); //recurse, note that the stack will never be deeper than 1 step since await returns..
    Handle(socket); // this will get called since "await" returns
}
在这个特定的示例中,代码使用异步等待tcp套接字,一旦被接受,它将递归并异步等待另一个套接字。
这似乎可以正常工作,因为 await 部分会使代码返回到调用者,从而不会导致堆栈溢出。
所以这里有两个问题:
1.如果我们忽略这个示例中处理的是套接字,只考虑自由堆栈递归,这种方式是否可行?还是我忽略了某些缺点?
2.从IO角度来看,上述代码是否足以处理所有传入的请求?即通过等待一个请求,一旦它被接受就开始等待另一个请求。这样做是否会导致某些请求失败?

1
那么 Handle(socket) 什么时候运行? - leppie
2
我个人认为这段代码本身没有什么问题,但是相比于更加简单明了的 public async void AwaitSocket() { while (true) { var socket = await this.AcceptSocketAsync(); Handle(socket); } },它有什么优势呢? - user743382
1
@RogerAlsing 你确定吗? await 可能会同步完成,在这种情况下,顺序会混乱。 - user743382
1
@RogerAlsing 不,正如我在早先的评论中提到的那样,await并不总是直接返回。它可能会直接返回,或者如果结果已经可用而无需等待,则可能直接处理结果。 - user743382
1
@hvd,这段代码对你的基于循环的代码的增强之处在于可以同时运行更多个Handle()实例。 - svick
显示剩余25条评论
1个回答

2

从上面的讨论中,我猜这样做是最好的方法。请给予反馈。

public async void StartAcceptingSockets()
{
    await Task.Yield(); 
    // return to caller so caller can start up other processes/agents
    // TaskEx.Yield in async ctp , Task.Yield in .net 4.5 beta

    while(true)
    {
        var socket = await this.AcceptSocketAsync();
        HandleAsync(socket); 
        //make handle call await Task.Yield to ensure the next socket is accepted as fast 
        //as possible and dont wait for the first socket to be completely handled
    } 
}

private async void HandleAsync(Socket socket)
{
      await Task.Yield(); // return to caller

      ... consume the socket here...
}

如果 AcceptSocketAsync()HandleAsync() 抛出异常会发生什么? - svick
如果HandleAsync在await yield部分后抛出异常会发生什么?如果继续处理在线程池中,并且代码抛出异常,则会终止线程池线程,对吗? - Roger Johansson
1
如果从“async void”中抛出异常,则会直接传递给“上下文”。如果继续在线程池上下文中运行,这将导致在线程池线程上直接引发异常,从而使进程崩溃。通常,除非它们是事件处理程序(因此必须void),否则应该使所有async方法返回Task/Task<TResult>。可以这样想:async void允许的,但不是推荐的 - Stephen Cleary
1
我也不认为需要使用 Yield。第一次调用 AcceptSocketAsync 几乎肯定会导致 yield。同样对于 HandleAsync:如果有工作要做,那么你可能会想直接去做;当你必须等待某些事情时,只需让 await 隐式地为你产生 yield 即可。 - Stephen Cleary
1
P.S. TcpListener.Start 可以带有一个 backlog 参数。默认情况下,它设置为最大值。backlog 是操作系统将代表您接受的连接数,因此您不必担心快速接受它们。我在博客文章中描述了这种行为。 - Stephen Cleary

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