我有以下代码(https://github.com/avinash0161/OrleansExperiments/tree/c0155b4b0c8c1bfe60aea8624f2cc83a52853dc7):
// Client code
Console.WriteLine("Client making a call");
var hashGenerator = client.GetGrain<IGrainA>(0);
hashGenerator.Call_A_ToTemp();
await Task.Delay(1000);
hashGenerator.Call_B_ToTemp();
// GrainA code
public async Task Call_A_ToTemp()
{
Console.WriteLine("Making call A to a fellow grain");
IGrainB grain = this.GrainFactory.GetGrain<IGrainB>(1);
grain.CallA().ContinueWith((t)=>
{
if(t.IsFaulted)
{
// Silo message timeout is 32s so t.IsFaulted is true
Console.WriteLine("Task Faulted");
Call_A_ToTemp();
}
});
}
public async Task Call_B_ToTemp()
{
Console.WriteLine("Making call B to a fellow grain");
IGrainB grain = this.GrainFactory.GetGrain<IGrainB>(1);
await grain.CallB();
}
// GrainB code
public async Task CallA()
{
Console.WriteLine("Call A came to GrainB");
await Task.Delay(34000); // more than timeout for the caller
}
public Task CallB()
{
Console.WriteLine("Call B came to GrainB");
return Task.CompletedTask;
}
此代码的输出为:
Client making a call
Making call A to a fellow grain
Call A came to GrainB
Making call B to a fellow grain
Task Faulted <---------------- This comes after Call_B_ToTemp executes
Making call A to a fellow grain
我们可以看到,在Call_A_ToTemp完全执行之前,Call_B_ToTemp已执行(稍后执行Call_A_ToTemp的ContinueueWith部分)。这是预期的吗?是否违反了谷物的单螺纹性质?
当我将Call_A_ToTemp()中的代码替换为:
public async Task Call_A_ToTemp()
{
Console.WriteLine("Making call A to a fellow grain");
IGrainB grain = this.GrainFactory.GetGrain<IGrainB>(1);
bool isSuccess = false;
while (! isSuccess)
{
try
{
await grain.CallA();
isSuccess = true;
} catch(TimeoutException){
Console.WriteLine("task faulted");
}
}
}
该代码现在保留了单线程性质,并且在执行Call_A_ToTemp()的所有ContinueWith
部分之前,不会调用Call_B_ToTemp。控制台输出如下:
Client making a call
Making call A to a fellow grain
Call A came to GrainB
Task Faulted
Making call A to a fellow grain
任何人都可以解释一下吗?有ContinueWith
时是否违反了单线程性质?
答案 0 :(得分:5)
不违反单线程性质。项目中的编译警告使问题的根源清晰可见。特别是:This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.
方法async Task Call_A_ToTemp()
从不等待对粒度B的调用。相反,它在发出调用后立即返回。由于Task
返回的Call_A_ToTemp()
立即完成,因此允许在该晶粒上执行另一个调用。 grain.CallA()
完成后,继续(ContinueWith(...)
)将尽快在谷物的TaskScheduler
上执行(例如,当谷物正在等待另一个呼叫或处于空闲状态时)。
相反,如果等待调用,或者如果从方法中删除了async
,并且代码更改为返回grain.CallA().ContinueWith(...)
调用,那么将观察到预期的行为。也就是说,将代码更改为此将为您带来预期的结果:
// removed 'async' here, since we're not awaiting anything.
// using 'async' is preferred, but this is to demonstrate a point about
// using ContinueWith and un-awaited calls
public Task Call_A_ToTemp()
{
Console.WriteLine("Making call A to a fellow grain");
IGrainB grain = this.GrainFactory.GetGrain<IGrainB>(1);
// Note the 'return' here
return grain.CallA().ContinueWith((t)=>
{
if(t.IsFaulted)
{
// Silo message timeout is 32s so t.IsFaulted is true
Console.WriteLine("Task Faulted");
Call_A_ToTemp();
}
});
}