如何在真相来源时同步访问权限#34;具有通用的多个操作

时间:2018-05-15 14:48:28

标签: c# multithreading design-patterns thread-safety thread-synchronization

我有一个项目存储库,可以从视图模型(主线程)和后台服务(后台线程)中使用。 两者都可以查询项目存储库并更新项目。

class ItemRepository
{
   Item GetItem(int id);

   void UpdateItem(Item item);
}

public class Item
{
    ItemState State { get; set; }
}

// Component A (view-model)
// Runs on main thread     
void ExecuteUpdateItemCommand()
{
   var item = _itemRepo.GetItem(1);
   if(item.State == ItemState.First)
   {
       itemState.State = ItemState.Second;
      _itemRepo.UpdateItem(item); // Ideally this should be on a separate thread but for now lets' assume it's on main thread
   }
}

// Component B (background service - receives notifications from backedn) 
// Runs on a background thread
void OnNotificationReceived()
{
   var item = _itemRepo.GetItem(1);
   if(item.State == ItemState.First)
   {
       item.State = GetNextState(); // some busines logic specific to Component B
       _itemRepo.UpdateItem(item);
    }
}

我遇到的问题是在两个线程中实现项目查询和更新的同步。

如果没有同步,项的状态可能会损坏,因为在if语句返回一个线程后,另一个线程可能正在将项状态更新为另一个值,这可能会破坏项状态的业务逻辑。

一个简单的解决方案是将组件A和组件B锁定在同一个共享对象上。从设计的角度来看,如何跨组件共享此对象(每个组件具有不同的目的)?

我看到的另一个解决方案是提供一个服务,它只是为了同步调用而同时执行组件A和组件B的业务逻辑,但在我看来这不是一个好的解决方案。

我正在寻找比这些更好的实现。

我的感觉是一个很好的解决方案可能是允许我在上下文中运行的东西。像

这样的东西
   ItemSyncContext.Run(ExecuteUpdateItemCommand);

   ItemSyncContext.Run(OnNotificationReceived);      

2 个答案:

答案 0 :(得分:0)

理想情况下,您只需要一次调用同时执行这两项更新的服务。如果这不是一个选项,您需要回答问题"更新何时发生?"在服务中。如果调用阻塞,那么在调用返回之前提交更新,您可以安全地保持代码不变。否则,您需要查看某种机制(即锁定)以确保以正确的顺序进行调用。

答案 1 :(得分:0)

如果您的存储库位于数据库(即SQL)之上,那么通过具有适当隔离级别的事务序列化访问可能会为同步端提供另一种方法:

// Component A (view-model)
// Runs on main thread     
void ExecuteUpdateItemCommand()
{
    try
    {           
       _itemRepo.BeginTransaction();

       var item = _itemRepo.GetItem(1);
       if(item.State == ItemState.First)
       {
           itemState.State = ItemState.Second;
          _itemRepo.UpdateItem(item); // Ideally this should be on a separate thread but for now lets' assume it's on main thread
       }
    }
    catch
    {
        if (_itemRepo.InTransaction)
        {
            _itemRepo.RollbackTransaction();
        }

        throw;
    }
    finally
    {
        if (_itemRepo.InTransaction)
        {
            _itemRepo.CommitTransaction();
        }
    }
}

// Component B (background service - receives notifications from backedn) 
// Runs on a background thread
void OnNotificationReceived()
{
    try
    {
        _itemRepo.BeginTransaction();

        var item = _itemRepo.GetItem(1);
        if(item.State == ItemState.First)
        {
           item.State = GetNextState(); // some busines logic specific to Component B
           _itemRepo.UpdateItem(item);
        }
    }
    catch
    {
        if (_itemRepo.InTransaction)
        {
            _itemRepo.RollbackTransaction();
        }

        throw;
    }
    finally
    {
        if (_itemRepo.InTransaction)
        {
            _itemRepo.CommitTransaction();
        }
    }
}