1. 程式人生 > >Orleans 2.0官方文件(閆輝的個人翻譯)——4.6 重入

Orleans 2.0官方文件(閆輝的個人翻譯)——4.6 重入

重入

grain啟用體是單執行緒的,預設情況下,啟用體會自始至終地處理完成每個請求後,才會處理下一個請求。在某些情況下,當一個請求等待非同步操作完成時,對一個啟用體來說,它可能需要處理其他請求。由於這個及其他的原因,Orleans為開發人員提供了對請求的交錯行為的一些控制。在以下情況下,可以交錯處理多個請求:

  • grain類標記為 [Reentrant]
  • 介面方法標記為 [AlwaysInterleave]
  • 同一個呼叫鏈中的請求
  • grain的MayInterleave謂詞返回true

以下各節將討論這些情況。

可重入的grain

grain

的實現類可以用[Reentrant]屬性標記,以指示不同的請求可以自由地被交錯。

換句話說,可重入的啟用體,可以在上一個請求尚未完成處理的情況下,開始執行另一個請求。執行仍然限於單個執行緒,因此啟用體仍然一次執行一個回合,並且每個回合僅代表啟用體的一個請求執行。

可重入的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.
    }
}