俳優にメッセージを渡すときに、「オブジェクト参照」の問題を回避するにはどうすればよいですか?
-
28-09-2019 - |
質問
しばらく前に私は名前の簡単なクラスをまとめました Actor
それが私の実装でした 俳優モデル. 。それ以来、私はそれを大成功で使用しました(差別された組合タイプの欠如のために、いくつかの迷惑な回避策を除いて。)。クラスを不格好で遅くすることなく解決する方法がわからないという問題が残っています。
誰かがメッセージを定義するとき、彼らはもちろん、発信者自身が操作できるオブジェクトへの参照を含める権利の範囲内です。私がこのクラスを使用している唯一の人物であるという知識があっても、これはまだ私を悩ませています。
これを回避する方法の良い例は ウェブワーカー Firefoxで実装されています。ワーカーにオブジェクトを渡すと、JSONにシリアル化されます。
何か案は?
public abstract class Actor<T, U> : IDisposable
{
private const int AsyncChannelPoolSize = 20;
private volatile bool _disposed;
private readonly Thread _actorThread;
private readonly AsyncReplyChannel<T, U> _messageChannel;
private readonly Lazy<ObjectPool<AsyncChannel<U>>> _asyncChannelPool;
public event EventHandler<ExceptionEventArgs> Exception;
protected Actor()
{
_messageChannel = new AsyncReplyChannel<T, U>();
_asyncChannelPool = new Lazy<ObjectPool<AsyncChannel<U>>>(() => new ObjectPool<AsyncChannel<U>>(AsyncChannelPoolSize));
_actorThread = new Thread(ProcessMessages);
_actorThread.IsBackground = true;
_actorThread.Start();
}
public U PostWithReply(T value)
{
ThrowIfDisposed();
var replyChannel = default(AsyncChannel<U>);
var replyPackage = default(AsyncReplyPackage<T, U>);
var replyMessage = default(U);
try
{
replyChannel = _asyncChannelPool.Value.Get();
replyPackage = new AsyncReplyPackage<T, U>(value, replyChannel);
_messageChannel.Send(replyPackage);
replyMessage = replyChannel.Receive();
}
finally
{
_asyncChannelPool.Value.Put(replyChannel);
}
return replyMessage;
}
public void PostWithAsyncReply(T value, IAsyncChannel<U> replyChannel)
{
ThrowIfDisposed();
_messageChannel.Send(new AsyncReplyPackage<T, U>(value, replyChannel));
}
public void Dispose()
{
Dispose(true);
}
protected abstract void ProcessMessage(AsyncReplyPackage<T, U> package);
protected virtual void OnException(Exception ex)
{
var exceptionEvent = Exception;
if (exceptionEvent != null)
{
exceptionEvent(this, new ExceptionEventArgs(ex));
}
}
protected virtual void Dispose(bool disposing)
{
_disposed = true;
_messageChannel.Dispose();
if (_asyncChannelPool.IsValueCreated)
{
_asyncChannelPool.Value.Dispose();
}
}
private void ProcessMessages()
{
var package = default(AsyncReplyPackage<T, U>);
while (_messageChannel.TryReceive(out package) && !_disposed)
{
try
{
ProcessMessage(package);
}
catch (Exception ex)
{
OnException(ex);
}
}
}
private void ThrowIfDisposed()
{
if (_disposed)
{
throw new ObjectDisposedException(GetType().FullName);
}
}
}
解決
これが良い解決策であるというわけではありませんが、制限することができます T
することが ICloneable
そして、あなたがそれを取得するときにオブジェクトをクローンします。それは、あなたが説明した「JSONへのシリアル化」アプローチに多かれ少なかれ同等のものです。言うまでもなく、これは不格好で遅いでしょう。
本当に、メッセージを不変に保つことを忘れないでください。 C#は、あなたのためにこれを非常にうまく実施できる言語ではありません。
他のヒント
私はあなたがここに問題のないものを持っていると思います - あなたの上に何らかの種類のシリアル化が起こっているなら _messageChannel
その後、Calleeは元のオブジェクトへの参照を取得せず、元のオブジェクトのコピーを取得します。
コールの完了時に「参照」が返されることが重要な場合は、クラス参照に依存するだけでなく、識別子を使用することを検討することをお勧めします。その識別子を使用して元のオブジェクトを見つけます。