我第一次玩async CTP时差不多15分钟......(很好)。
这是一个非常简单的服务器,我已经敲了一下:
internal class Server
{
private HttpListener listener;
public Server()
{
listener = new HttpListener();
listener.Prefixes.Add("http://*:80/asynctest/");
listener.Start();
Go();
}
async void Go()
{
HttpListenerContext context = await listener.GetContextAsync();
Go();
using (var httpListenerResponse = context.Response)
using (var outputStream = httpListenerResponse.OutputStream)
using (var sw = new StreamWriter(outputStream))
{
await sw.WriteAsync("hello world");
}
}
}
可以看出,异步方法Go
会调用自身。在经典的非异步世界中,这会导致堆栈溢出。我认为这不是异步方法的情况,但我想确定,不管怎样。任何人吗?
答案 0 :(得分:13)
让我们把它分解成更简单的东西:
async static void Go()
{
await Something();
Go();
await SomethingElse();
}
编译器如何处理这个?
基本上这就像这个草图:
class HelperClass
{
private State state = STARTSTATE;
public void DoIt()
{
if (state == STARTSTATE) goto START;
if (state == AFTERSOMETHINGSTATE) goto AFTERSOMETHING;
if (state == AFTERSOMETHINGELSESTATE) goto AFTERSOMETHINGELSE;
START:
{
state = AFTERSOMETHINGSTATE;
var awaiter = Something().MakeAnAwaiter();
awaiter.WhenDoneDo(DoIt);
return;
}
AFTERSOMETHING:
{
Go();
state = AFTERSOMETHINGELSESTATE;
var awaiter = SomethingElse().MakeAnAwaiter();
awaiter.WhenDoneDo(DoIt);
return;
}
AFTERSOMETHINGELSE:
return;
}
static void Go()
{
var helper = new HelperClass();
helper.DoIt();
}
现在你要记住的是,当每个异步操作完成时,“DoIt”被安排由消息循环再次调用(当然,在帮助器的适当实例上)。
那会发生什么?解决它。你第一次给Go打电话。这使助手排名第一,并调用DoIt。这会调用Something(),返回任务,为该任务做一个等待,告诉等待者“当你完成时,调用helper1.DoIt”然后返回。
十分之一秒后,任务完成,消息循环调用helper1的DoIt。 helper1的状态是AFTERSOMETHINGSTATE,所以我们取goto并调用Go。这使得helper2并在其上调用DoIt。这会调用Something(),返回任务,为该任务做一个等待,告诉等待者“当你完成时,在helper2上调用DoIt”并将控制权返回给helper1的DoIt。这会调用SomethingElse,为该任务做一个等待,然后告诉它“当你完成其他事情时,请调用helper1的DoIt”。然后它返回。
现在我们有两个未完成的任务,堆栈上没有代码。其中一项任务将首先完成。假设SomethingElse任务首先完成。消息循环调用helper1.DoIt(),它立即返回。 Helper1现在是垃圾。
稍后消息循环调用helper2.DoIt(),并分支到AFTERSOMETHING。现在调用Go(),它创建了helper3 ...
所以不,这里没有无限的递归。每次Go执行时,它都会异步启动Something(),然后返回其调用者。在“某事”之后发生对这些事情的调用。 “Go”一次只能在堆栈上出现。