觀察者

在某些情況下,簡單的訊息/回應模式是不夠的,且用戶端需要接收非同步通知。 例如,當朋友發佈新的即時訊息時,使用者可能會想要收到通知。

用戶端觀察者是允許以非同步方式通知用戶端的機制。 觀察者介面必須繼承自 IGrainObserver,而且所有方法都必須傳回 voidTaskTask<TResult>ValueTaskValueTask<TResult>。 不建議使用 void 傳回型別,因為如果方法擲回例外狀況,可能會造成應用程式損毀,因此建議在實作時使用 async void。 相反地,對於盡力而為的通知情節,請考慮將 OneWayAttribute 套用至觀察者的介面方法。 這會導致接收者不會傳送方法叫用的回應,而且會導致方法在呼叫網站立即傳回,而不會等待觀察者的回應。 粒紋會透過叫用觀察者上的方法來呼叫方法,就像任何粒紋介面方法一樣。 Orleans 執行階段可確保要求和回應的傳遞。 觀察者常見的使用案例是登錄用戶端,以在 Orleans 應用程式中發生事件時接收通知。 發佈這類通知的粒紋應該提供 API 來新增或移除觀察者。 此外,將方法公開通常很方便,這類方法會允許將現有訂用帳戶取消。

粒紋開發人員可以使用公用程式類別,例如 ObserverManager<TObserver>,簡化觀察到的粒紋型別開發。 與在失敗後視需要自動重新啟動的粒紋不同,用戶端無法容錯:失敗的用戶端可能永遠不會復原。 基於這個理由,ObserverManager<T> 公用程式會在設定的持續時間之後移除訂用帳戶。 作用中的用戶端應該在計時器上重新訂閱,使其訂用帳戶保持作用中。

若要訂閱通知,用戶端必須先建立實作觀察者介面的本地物件。 然後其會在觀察者中心 (CreateObjectReference) 上呼叫方法,將物件轉換成粒紋參考,您可以將該粒紋參考傳遞至通知粒紋時所用的訂用帳戶方法。

其他粒紋也可以使用此模型來接收非同步通知。 粒紋也可以實作 IGrainObserver 介面。 與用戶端訂用帳戶案例不同,訂閱粒紋只會實作觀察者介面,並在參考中傳遞至本身 (例如 this.AsReference<IMyGrainObserverInterface>())。 因為粒紋已經可定址,所以不需要 CreateObjectReference()

程式碼範例

假設我們有一個定期將訊息傳送給用戶端的粒紋。 為了簡單起見,範例中的訊息會是字串。 我們會先在接收訊息的用戶端上定義介面。

介面看起來像這樣

public interface IChat : IGrainObserver
{
    Task ReceiveMessage(string message);
}

唯一的特殊事項是介面應繼承自 IGrainObserver。 現在,任何想要觀察那些訊息的用戶端都應該實作實作 IChat 的類別。

最簡單的案例看起來類似如下:

public class Chat : IChat
{
    public Task ReceiveMessage(string message)
    {
        Console.WriteLine(message);
        return Task.CompletedTask;
    }
}

在伺服器上,我們接下來應該有一個將這些聊天訊息傳送給用戶端的粒紋。 粒紋也應該有一個機制,可讓用戶端自行訂閱和取消訂閱通知。 對於訂用帳戶,粒紋可以使用公用程式類別 ObserverManager<TObserver> 的執行個體。

注意

ObserverManager<TObserver> 自 7.0 版起為 Orleans 的一部分。 對於較舊的版本,可以複製下列實作

class HelloGrain : Grain, IHello
{
    private readonly ObserverManager<IChat> _subsManager;

    public HelloGrain(ILogger<HelloGrain> logger)
    {
        _subsManager =
            new ObserverManager<IChat>(
                TimeSpan.FromMinutes(5), logger);
    }

    // Clients call this to subscribe.
    public Task Subscribe(IChat observer)
    {
        _subsManager.Subscribe(observer, observer);

        return Task.CompletedTask;
    }

    //Clients use this to unsubscribe and no longer receive messages.
    public Task UnSubscribe(IChat observer)
    {
        _subsManager.Unsubscribe(observer);

        return Task.CompletedTask;
    }
}

若要將訊息傳送給用戶端,可以使用 ObserverManager<IChat> 執行個體的 Notify 方法。 此方法會採用 Action<T> 方法或 Lambda 運算式 (其中 T 在此的型別為 IChat)。 您可以在介面上呼叫任何方法,將其傳送給用戶端。 在案例中,我們只有一個方法 (ReceiveMessage),而伺服器上的傳送程式碼看起來像這樣:

public Task SendUpdateMessage(string message)
{
    _subsManager.Notify(s => s.ReceiveMessage(message));

    return Task.CompletedTask;
}

現在,伺服器有方法可將訊息傳送給觀察者用戶端、訂閱/取消訂閱有兩種方法,而用戶端已實作能夠觀察粒紋訊息的類別。 最後一個步驟是使用先前實作的 Chat 類別,在用戶端上建立觀察者參考,並在訂閱之後接收訊息。

程式碼應該會看起來如下:

//First create the grain reference
var friend = _grainFactory.GetGrain<IHello>(0);
Chat c = new Chat();

//Create a reference for chat, usable for subscribing to the observable grain.
var obj = _grainFactory.CreateObjectReference<IChat>(c);

//Subscribe the instance to receive messages.
await friend.Subscribe(obj);

現在,每當伺服器上的粒紋呼叫 SendUpdateMessage 方法時,所有已訂閱的用戶端都會收到訊息。 在用戶端程式碼中,變數 c 中的 Chat 執行個體將會收到訊息,並將其輸出至主控台。

重要

傳遞至 CreateObjectReference 的物件會透過 WeakReference<T> 保留,因此如果沒有任何其他參考存在,則會進行記憶體回收。

使用者應該針對不想要收集的每個觀察者維護參考。

注意

觀察者本質上是不可靠的,因為裝載觀察者的用戶端可能會失敗,且復原後所建立的觀察者有不同的 (隨機化) 身分識別。 如上所述,ObserverManager<TObserver> 依賴觀察者的定期重新訂閱,以便可以移除非作用中的觀察者。

執行模型

IGrainObserver 的實作是透過呼叫 IGrainFactory.CreateObjectReference 來註冊,而該方法的每個呼叫都會建立指向該實作的新參考。 Orleans 將會執行向每個參考傳送的要求,逐一傳送至完成。 觀察者是不可重入的,因此 Orleans 不會將對觀察者的並行要求交錯。 如果有多個觀察者同時接收要求,可以平行執行這些要求。 觀察者方法的執行不受 ReentrantAttributeAlwaysInterleaveAttribute 之類的屬性影響:開發人員無法自訂執行模型。