Orleans 2.0 官方文档 —— 4.6 Grains -> 重入

重入

grain激活体是单线程的,默认情况下,激活体会自始至终地处理完成每个请求后,才会处理下一个请求。在某些情况下,当一个请求等待异步操作完成时,对一个激活体来说,它可能需要处理其他请求。由于这个及其他的原因,Orleans为开发人员提供了对请求的交错行为的一些控制。在以下情况下,可以交错处理多个请求:

  • grain类标记为 [Reentrant]
  • 接口方法标记为 [AlwaysInterleave]
  • 同一个调用链中的请求
  • grain的MayInterleave谓词返回true

以下各节将讨论这些情况。

可重入的grain

grain的实现类可以用[Reentrant]属性标记,以指示不同的请求可以自由地被交错。

换句话说,可重入的激活体,可以在上一个请求尚未完成处理的情况下,开始执行另一个请求。执行仍然限于单个线程,因此激活体仍然一次执行一个回合,并且每个回合仅代表激活体的一个请求执行。

可重入的grain代码永远不会并行运行多段grain代码(grain代码的执行将始终是单线程的),但是,可重入的grain可能会看到不同请求交错执行的代码。也就是说,来自不同请求的延续回合,是交错执行的。

例如,下面的伪代码,当Foo和Bar是同一个grain类的2个方法时:

Task Foo()
{
    await task1;    // line 1
    return Do2();   // line 2
}

Task Bar()
{
    await task2;   // line 3
    return Do2();  // line 4
}

如果这个grain被标记[Reentrant],则Foo和Bar的执行可能会交错。

例如,以下执行顺序是可能的:

第1行,第3行,第2行和第4行。即,来自不同请求的回合发生了交错。

如果grain不是可重入的,则唯一可能的执行是:第1行,第2行,第3行,第4行。或者:第3行,第4行,第1行,第2行(新请求无法在上一个完成之前开始)。

在选择grain可重入和不可重入时,主要的权衡是代码的复杂性(要使交错正确地工作),以及推理它的难度。

在一个微不足道的情况下,当grain是无状态,并且逻辑简单时,那么更少的可重入grain,通常会稍微高效一些(但不能太少,以便使用所有硬件线程)。

如果代码更复杂,大量的不可重入的grain,即使整体效率稍低一些,也会为您省去许多查找出不明显的交错问题时的痛苦。

最终的答案取决于应用程序的具体情况。

交错方法

不管谷grain是否可重入,标记为[AlwaysInterleave]的grain 接口的方法都将交错。请考虑以下示例:

public interface ISlowpokeGrain : IGrainWithIntegerKey
{
    Task GoSlow();

    [AlwaysInterleave]
    Task GoFast();
}

public class SlowpokeGrain : Grain, ISlowpokeGrain
{
    public async Task GoSlow()
    {
        await Task.Delay(TimeSpan.FromSeconds(10));
    }

    public async Task GoFast()
    {
        await Task.Delay(TimeSpan.FromSeconds(10));
    }
}

现在考虑以下客户端请求启动的调用流程:

var slowpoke = client.GetGrain<ISlowpokeGrain>(0);

// A) This will take around 20 seconds
await Task.WhenAll(slowpoke.GoSlow(), slowpoke.GoSlow());

// B) This will take around 10 seconds.
await Task.WhenAll(slowpoke.GoFast(), slowpoke.GoFast(), slowpoke.GoFast());

呼叫GoSlow不会交错,因此两次GoSlow()呼叫的执行大约需要20秒。另一方面,因为GoFast被标记[AlwaysInterleave],对它的三次调用将同时执行,并且将在大约10秒内完成,而不需要至少30秒完成。

调用链中的重入

为了避免死锁,调度程序允许在给定的调用链中重入。考虑以下两个具有相互递归方法的grain的例子,即IsEvenIsOdd

public interface IEvenGrain : IGrainWithIntegerKey
{
    Task<bool> IsEven(int num);
}

public interface IOddGrain : IGrainWithIntegerKey
{
    Task<bool> IsOdd(int num);
}

public class EvenGrain : Grain, IEvenGrain
{
    public async Task<bool> IsEven(int num)
    {
        if (num == 0) return true;
        var oddGrain = this.GrainFactory.GetGrain<IOddGrain>(0);
        return await oddGrain.IsOdd(num - 1);
    }
}

public class OddGrain : Grain, IOddGrain
{
    public async Task<bool> IsOdd(int num)
    {
        if (num == 0) return false;
        var evenGrain = this.GrainFactory.GetGrain<IEvenGrain>(0);
        return await evenGrain.IsEven(num - 1);
    }
}

现在考虑以下客户端请求启动的调用流程:

var evenGrain = client.GetGrain<IEvenGrain>(0);
await evenGrain.IsEven(2);

上面的代码调用IEvenGrain.IsEven(2)IsEven(2)又调用IOddGrain.IsOdd(1),然后IsOdd(1)又调用IEvenGrain.IsEven(0),而IsEven(0)返回true,通过调用链最终返回给客户端。如果没有调用链重入,当IOddGrain调用IEvenGrain.IsEven(0)时,上面的代码将导致死锁。然而,通过调用链重入,允许调用继续进行,因为它被认为是开发者的意图。

通过将SchedulingOptions.AllowCallChainReentrancy设置false,可以禁用此行为。例如:

siloHostBuilder.Configure<SchedulingOptions>(
    options => options.AllowCallChainReentrancy = false);

使用谓词重入

grain类可以通过检查请求来指定一个谓词,此谓词用于在挨个调用的基础上确定交错。[MayInterleave(string methodName)]属性提供此功能。该属性的参数是grain类中静态方法的名称,该方法接受一个InvokeMethodRequest对象并返回一个bool,指示请求是否应该交错。

下面是一个示例,如果请求的参数类型具有[Interleave]属性,则允许交错:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public sealed class InterleaveAttribute : Attribute { }

// Specify the may-interleave predicate.
[MayInterleave(nameof(ArgHasInterleaveAttribute))]
public class MyGrain : Grain, IMyGrain
{
    public static bool ArgHasInterleaveAttribute(InvokeMethodRequest req)
    {
        // Returning true indicates that this call should be interleaved with other calls.
        // Returning false indicates the opposite.
        return req.Arguments.Length == 1
            && req.Arguments[0]?.GetType().GetCustomAttribute<InterleaveAttribute>() != null;
    }

    public Task Process(object payload)
    {
        // Process the object.
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值