普通C#和Orleans中的方法调用之间的区别

时间:2018-08-03 01:23:22

标签: c# asp.net async-await orleans

我在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语句时返回。

1 个答案:

答案 0 :(得分:9)

我将分两部分进行回答:

  1. 为什么不希望在某个远程呼叫的第一个await之前阻塞
  2. 所见即所得

从一开始: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时,您的线程才会被阻塞。

相关问题