我使用的是使用光纤的网络库。光纤确保以同步和有序的方式执行所有已执行的操作:
interface IFiber
{
Enqeue(Action action)
}
每个连接的对等体都有其请求光纤,在其中执行所有操作。
我也有一个应用级光纤。
每个对等体都有其(持久性)数据实体并对其进行操作。但我也需要从外部对等环境中访问它,即使它已断开连接(并且它的光纤自动处理)。
因此,当应用光纤(对等断开连接)或新光纤(对等连接)替换光纤时,我应该以某种方式维护字典并在光纤之间传输qeued操作。
我认为每个实体都存储一个Executor类。执行程序Enqeues操作,(De)RegisterFiber并在当前光纤内部执行操作。
public class Executor
{
readonly object _lock = new object();
readonly IFiber _applicaitonFiber;
IFiber _currentFiber;
Action _actions;
public Executor(IFiber applicaitonFiber)
{
_currentFiber = _applicaitonFiber = applicaitonFiber;
}
public void SetFiber(IFiber fiber)
{
lock (_lock)
{
var fiberLocal = _currentFiber = fiber ?? _applicaitonFiber;
if (_actions != null)
_currentFiber.Enqueue(() => Execute(fiberLocal));
}
}
public void Enqeue(Action action)
{
if (action == null) throw new ArgumentNullException("action");
lock (_lock)
{
bool start = _actions == null;
_actions += action;
var fiberLocal = _currentFiber;
if (start)
_currentFiber.Enqueue(() => Execute(fiberLocal));
}
}
void Execute(IFiber currentFiber)
{
lock (_lock)
{
if (currentFiber != _currentFiber) return;
var a = _actions;
if (a == null) return;
_actions = null;
// I can't release lock here. What if new fiber is registered before it is executed?
a();
}
}
}
问题是如何在先前注册的光纤上执行操作时阻止新的光纤注册。
考虑这个死锁示例:
我认为一种可能的解决方案是使SetFiber方法异步并通过_applicationFiber进行所有操作。
public class Executor
{
readonly object _lock = new object();
readonly IFiber _applicationFiber;
IFiber _currentFiber;
Action _actions;
public Executor(IFiber applicaitonFiber)
{
_currentFiber = _applicationFiber = applicaitonFiber;
}
public IOperationResult<bool> SetFiber(IFiber fiber)
{
var r = new OperationResult<bool>();
_applicationFiber.Enqueue(
() =>
{
lock (_lock)
{
var fiberLocal = _currentFiber = fiber ?? _applicationFiber;
if (_actions != null)
_currentFiber.Enqueue(() => Execute(fiberLocal));
r.Result = true; // async event
}
});
return r;
}
public void Enqeue(Action action)
{
if (action == null) throw new ArgumentNullException("action");
_applicationFiber.Enqueue(
() =>
{
lock (_lock)
{
bool start = _actions == null;
_actions += action;
var fiberLocal = _currentFiber;
if (start)
_currentFiber.Enqueue(() => Execute(fiberLocal));
}
});
}
void Execute(IFiber currentFiber)
{
lock (_lock)
{
if (currentFiber != _currentFiber) return; // replaced
var a = _actions;
if (a == null) return;
_actions = null;
a();
}
}
}
但我仍然不确定这个解决方案。如果我需要从动作内部执行大型数据库查询,该怎么办?它可以暂停整个应用光纤,直到锁定被释放。
我可以在这里申请任何模式吗?
答案 0 :(得分:0)
我认为它应该有效:
using System;
using System.Threading;
using ExitGames.Concurrency.Fibers;
public class EntityFiberManager
{
readonly object _executionLock = new object();
//readonly object _enqueueLock = new object();
readonly IFiber _applicationFiber;
IFiber _currentFiber;
volatile Action _actions;
public EntityFiberManager(IFiber applicaitonFiber)
{
_currentFiber = _applicationFiber = applicaitonFiber;
}
/// <summary>
/// Removes the current set fiber if it's equal to <paramref name="fiber"/>.
/// All queued actions will be rerouted to the application fiber.
/// Can be called from anywhere.
/// Disposed fiber should never be set with <see cref="AcquireForNewFiber"/> again.
/// Doesn't block.
/// </summary>
public void ReleaseForDisposedFiber(IFiber fiber)
{
if (fiber == null) throw new ArgumentNullException("fiber");
ReleaseForDisposedFiberInternal(fiber);
}
private void ReleaseForDisposedFiberInternal(IFiber fiber)
{
if ((_executingEntityFiberManager != null && _executingEntityFiberManager != this) || !Monitor.TryEnter(_executionLock, 1))
{
_applicationFiber.Enqueue(() => ReleaseForDisposedFiberInternal(fiber));
return;
}
try
{
//lock (_enqueueLock)
//{
if (_currentFiber != fiber) return;
_currentFiber = null;
Thread.MemoryBarrier(); // do not reorder!
if (_actions != null)
_applicationFiber.Enqueue(() => Execute(null));
//}
}
finally
{
Monitor.Exit(_executionLock);
}
}
/// <summary>
/// Sets a new fiber.
/// All queued actions will be rerouted to that fiber.
/// Can be called from anywhere except from another Executor queud action.
/// Blocks until the current execution of queued actions is not finished.
/// </summary>
public void AcquireForNewFiber(IFiber fiber)
{
if (fiber == null) throw new ArgumentNullException("fiber");
if (_executingEntityFiberManager != null && _executingEntityFiberManager != this)
throw new InvalidOperationException("Can't call this method on from queued actions on another instance");
lock (_executionLock)
//lock (_enqueueLock)
{
if (_currentFiber == fiber) return;
var fiberLocal = _currentFiber = fiber;
Thread.MemoryBarrier(); // do not reorder!
if (_actions != null)
fiberLocal.Enqueue(() => Execute(fiberLocal));
}
}
/// <summary>
/// Enqueus an action to the current fiber.
/// Doesn't block.
/// </summary>
public void Enqeue(Action action)
{
if (action == null) throw new ArgumentNullException("action");
//lock (_enqueueLock)
//{
// we could add another lock
// but we just need to ensure
// that we properly detect when previous queue was empty
// also delegate are immutable so we exchange references
Action currentActions;
Action newActions;
do
{
Thread.Sleep(0);
currentActions = _actions;
newActions = currentActions + action;
}
while (Interlocked.CompareExchange(ref _actions, newActions, currentActions) != currentActions);
bool start = currentActions == null;
if (start)
{
// that's why we would want to use _enqueueLock
// we don't want the current fiber to be replaced
// imagine that when replacing queue was empty
// than we read the fiber
var fiber = _currentFiber;
Thread.MemoryBarrier();
if (fiber == null)
fiber = _applicationFiber;
// and then replace writes its new fiber to memory
// we have a wrong fiber here
// and Execute will just quit
// and all next Enqueue calls will do nothing
// but now it's fixed with MemoryBarrier call. I think so.
fiber.Enqueue(() => Execute(fiber));
}
//}
}
[ThreadStatic]
static EntityFiberManager _executingEntityFiberManager;
void Execute(IFiber currentFiber)
{
lock (_executionLock)
{
if (currentFiber != _currentFiber) return; // replaced
var actions = Interlocked.Exchange(ref _actions, null);
if (actions == null) return;
if (_executingEntityFiberManager != null)
throw new InvalidOperationException("Already in execution process");
_executingEntityFiberManager = this;
try
{
actions();
}
finally
{
_executingEntityFiberManager = null;
}
}
}
}