【问题标题】:How to handle many concurrently interacting entities in C#如何在 C# 中处理多个并发交互的实体
【发布时间】:2013-07-19 14:50:11
【问题描述】:

我正在开发一个应用程序,它具有许多可以同时交互的不同实体。我想知道这些实体以线程安全的方式相互交互的最佳方式是什么。

为了用一些简化的代码进行演示,请考虑每个实体都有自己的光纤和一些状态:

class Fiber
{
    private ActionBlock<Action> _workQueue;

    public Fiber()
    {
        _workQueue = new ActionBlock<Action>((a) => a());
    }

    public void Enqueue(Action a)
    {
        _workQueue.Post(a);
    }

    public void Stop()
    {
        _workQueue.Complete();
    }
}

class EntityState
{
    public int x { get; set; }
}

class Entity
{
    private Fiber _fiber = new Fiber();

    public EntityState State { get; set; }

    // ...
}

假设动作被任意排入实体纤程。一个这样的动作可能是一个实体必须修改另一个实体的状态。我考虑过两种选择以线程安全的方式执行此操作。

选项 1:仅允许通过线程安全包装器 I.E. 进行状态突变

class Entity
{
    private Fiber _fiber = new Fiber();

    private ReaderWriterLockSlim _stateLock = new ReaderWriterLockSlim();
    private EntityState _state = new EntityState();

    public T ReadState<T>(Func<EntityState, T> reader)
    {
        T result = default(T);

        _stateLock.EnterReadLock();
        result = reader(_state);
        _stateLock.ExitReadLock();

        return result;
    }

    public void WriteState(Action<EntityState> writer)
    {
        _stateLock.EnterWriteLock();
        writer(_state);
        _stateLock.ExitWriteLock();
    }

    // ...
}

选项 2:仅通过将状态更改调度到拥有实体的光纤上并返回 Future 来允许状态更改,以便更改器可以看到更改发生的时间,即 I.E.

class Future<T>
{
    public T Value { get; set; }
}

class Entity
{
    private Fiber _fiber = new Fiber();

    private EntityState _state = new EntityState();

    public Future<T> AccessState<T>(Func<EntityState, T> accessor)
    {
        Future<T> future = new Future<T>();

        _fiber.Enqueue(() => future.Value = accessor(_state));

        return future;
    }

    // ...
}

我还没有考虑过哪些其他选项?有没有好的方法来做到这一点?我应该这样做吗?

【问题讨论】:

    标签: c# .net concurrency


    【解决方案1】:

    你所有的选择都会给你带来痛苦。

    1. 即使代码在技术上是正确的,您也会在您的域中遇到逻辑竞争情况。订单之类的东西可以在付款之前发货。
    2. 这会损害可维护性。线程代码难以调试、难以测试、难以阅读。当它与应用程序交错时,这会变得更加复杂。

    正确的方法是将线程代码与应用程序代码完全分离。将任务放在单线程光纤中。任务在所有涉及的实体中同步执行所有作业。任务完成后,可以异步执行IO。 我已经为这种方法写了a library

    【讨论】:

    • 感谢您的意见。你的两个观点都是绝对正确的,我已经开始觉得编写代码就像穿过一片地雷;)。该项目是一个游戏服务器,这就是我选择高并发的原因。我的目标是每 50 毫秒运行一次更新,因此同步更新每个实体确实会损害性能。如果一个实体更新缓慢,其他实体不会受到影响,这一点很重要。
    【解决方案2】:

    您可以将突变加入到拥有的 Fiber 中,然后将其延续加入到您自己的 Fiber 中。这样你就没有任何显式锁。

    但是:这种 Fiber 方法并不比在访问实体之前只使用 lock 更好。 (锁内部包含一个队列。)

    此外,您不能以这种方式进行跨实体交易。使用锁方法,您可以收集参与事务的所有实体的锁,将它们排序为总顺序并将它们全部锁定。这为您提供了没有死锁的跨实体事务。

    【讨论】:

    • 感谢您的意见。我或多或少地按照你提到的那样做(见我的回答)。我正在使用光纤,因为每个实体都使用异步套接字从网络流中接收数据。我将它们安排在一根光纤上,以确保它们按到达顺序进行处理。
    【解决方案3】:

    目前,我或多或少地选择了选项 2,但如果访问状态不会阻塞,我会同步设置结果。即。

    using System;
    using System.Threading;
    
    namespace Server.Utility
    {
        public class ThreadSafeWrapper<ObjectType>
        {
            private ObjectType m_object;
            private Fiber m_fiber;
    
            public ThreadSafeWrapper(ObjectType obj, Fiber fiber)
            {
                m_object = obj;
                m_fiber = fiber;
            }
    
            public Future<ReturnType> Transaction<ReturnType>(Func<ObjectType, ReturnType> accessor)
            {
                Future<ReturnType> future = new Future<ReturnType>();
    
                ReturnType synchronousResult = default(ReturnType);
                if (Monitor.TryEnter(m_object))
                {
                    synchronousResult = accessor(m_object);
                    Monitor.Exit(m_object);
    
                    future.SetResult(synchronousResult);
                }
                else
                {
                    m_fiber.Enqueue(() =>
                    {
                        ReturnType result = default(ReturnType);
                        lock (m_object)
                        {
                            result = accessor(m_object);
                        }
                        future.SetResult(result);
                    });
                }
    
                return future;
            }
    
            public void Transaction(Action<ObjectType> accessor)
            {
                if (Monitor.TryEnter(m_object))
                {
                    accessor(m_object);
                    Monitor.Exit(m_object);
                }
                else
                {
                    m_fiber.Enqueue(() =>
                    {
                        lock (m_object)
                        {
                            accessor(m_object);
                        }
                    });
                }
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-03-22
      • 1970-01-01
      • 2015-11-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-05-05
      相关资源
      最近更新 更多