我在localHost Clustering模式下运行Orleans,目前有1个Grain和一个客户端。
// client code
for (int i = 0; i <num_scan; ++i)
{
Console.WriteLine("client " + i);
// the below call should have returned when first await is hit in foo()
// but it doesn't work like that
grain.foo(i);
}
// grain code
async Task foo(int i)
{
Console.WriteLine("grain "+i);
await Task.Delay(2000);
}
其输出如下:
client 0
client 1
client 2
client 3
client 4
client 5
client 6
grain 0
client 7
client 8
client 9
client 10
grain 8
grain 7
.
.
在normal C#中,异步函数仅在命中await
时返回。在这种情况下,谷物产量应该是连续的。正如我们在上面看到的那样,谷物产量是乱序的。因此,任务将在命中await
语句之前返回。 我的问题是,奥尔良中的方法调用与常规C#之间有什么区别。
我看到this post提出了类似的问题,并且答复表明这两种方法调用情况不同,因为我们在奥尔良调用接口。 我想知道,该方法调用何时在奥尔良返回。
PS:我用await grain.foo()
尝试了上面的代码,它按顺序打印了谷物的输出。但是这种方法的问题是,await仅在整个foo()完成时才返回,而我希望它在命中await语句时返回。
答案 0 :(得分:9)
我将分两部分进行回答:
await
之前阻塞从一开始:Orleans是正常的C#,但是关于C#在这种情况下如何工作的假设缺少一些细节(下面将进行解释)。奥尔良专为可扩展的分布式系统而设计。有一个基本的假设,即如果您在某些谷物上调用方法,则该谷物当前可能在单独的机器上被激活。即使位于同一台计算机上,每个谷物也通常与其他谷物异步运行,通常在单独的线程上。
await
之前阻塞如果一台计算机呼叫另一台计算机,则需要花费一些时间(例如,由于网络原因)。
因此,如果您在一台计算机上有一个线程在调用另一台计算机上的对象,并且想要阻塞该线程直到该对象内的await
语句,那么您将在相当长的时间内阻塞该线程。线程将必须等待网络消息到达远程计算机,以便在远程启动谷物时对其进行调度,直到第一个await
为止执行谷物,然后再由远程计算机发送通过网络返回到第一台计算机的消息,说“嘿,第一次等待已被击中”。
像这样的阻塞线程不是可扩展的方法,因为CPU要么在线程阻塞时处于空闲状态,要么必须创建许多(昂贵的)线程才能使CPU忙于处理请求。就预分配的堆栈空间和其他数据结构而言,每个线程都要付出代价,而在线程之间进行切换则要为CPU付出代价。
因此,希望现在很清楚了,为什么在远程粒度达到其第一个await
之前不希望阻塞调用线程。现在,让我们看看如何在奥尔良中不阻塞线程。
请考虑您的grain
对象不是您编写的grain 实现类的实例,而是它是“纹理引用”。
您使用以下代码创建grain
对象:
var grain = grainFactory.GetGrain<IMyGrainInterface>("guest@myservice.com");
您从GetGrain
返回的对象是 grain引用。它实现了IMyGrainInterface
,但是它不是您编写的Grain类的实例。相反,它是奥尔良为您生成的类。此类是您要调用的远程粒度的表示形式,是对它的引用。
因此,当您编写类似以下的代码时:
grain.foo(i);
发生的事情是生成的类调用Orleans运行时以向远程谷物激活发出foo
请求。
作为示例,生成的代码实际上可能是这样的:
public Task foo(int i)
{
return base.InvokeMethodAsync(118718866, new object[]{ i });
}
这些详细信息对您隐藏了,但是如果您查看项目中的obj
目录下的内容,则可以查找它们。
因此您可以看到在生成的await
方法中实际上根本没有foo
!它只是要求Orleans运行时调用带有一些奇怪的整数和一些对象数组的方法。
在远程端,类似的生成类则相反:它接受您的请求并将其转换为对您编写的实际Grain代码的直接方法调用。在远程系统中,线程将一直执行到您的谷物代码中的第一个await
,然后将执行回馈给调度程序,就像在“普通C#”中一样。
此外:在RPC术语中,grain引用大致等效于代理对象:即,它是代表的对象。为传统的RPC框架(如WCF或gRPC)编写的代码与Orleans的行为相同:在客户端调用服务器上的方法之前,直到第一个await
时,您的线程才会被阻塞。