Orleans单线程性质不受ContinueWith的尊重

Avi*_*mar 1 c# asp.net actor orleans

我有以下代码(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;
}
Run Code Online (Sandbox Code Playgroud)

此代码的输出是:

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
Run Code Online (Sandbox Code Playgroud)

我们可以看到,Call_B_ToTemp在Call_A_ToTemp完全执行之前执行(ContinueWith稍后执行Call_A_ToTemp的一部分).这是预期的,是否违反了谷物的单螺纹特性?


当我用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");
       }

    }
}
Run Code Online (Sandbox Code Playgroud)

代码现在保留了单线程特性,并且ContinueWith在执行Call_A_ToTemp()的所有部分之前不调用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
Run Code Online (Sandbox Code Playgroud)

有人可以解释一下吗?有没有违反单线程性质的情况ContinueWith

Reu*_*ond 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()完成,continuation(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();
     }
    });
}
Run Code Online (Sandbox Code Playgroud)