在dotnet核心后台服务中处理实时系统中共享状态的最佳方法

问题描述

我在dotnet core 3.1中有一个后台服务IHostedService,该服务使用套接字(本地滚动)接收来自数百个客户端(工厂中的计算机)的请求。我的问题是,可以在访问对象(共享状态)的类的同一方法的不同线程上进行多次调用。这在代码库中很常见。请求也必须以正确的顺序处理。

它不在数据库中的原因是由于性能原因(实时系统)。我知道我可以使用锁,但是我不想在整个代码库中都拥有锁。

处理这种情况的标准方法是什么。您是否使用内存数据库?内存缓存?还是我只需要到处添加锁?

public class Machine
{
    
    public Machinestate {get; set;}

    // Gets called by multiple threads from multiple clients 
    public bool CheckMachinestatus()
    {
        return Machinestate.IsRunning;
    }

    // Gets called by multiple threads from multiple clients 
    public void SetMachinestatus()
    {
        Machinestate = Stopped;
    }
}

更新

这是一个例子。我有一个控制台应用程序,该应用程序通过插座与机器对话,以称量产品。控制台应用程序初始化时,它将把数据加载到内存中(有关被称重产品的信息)。所有这些都是在主线程上完成的,以保持数据完整性。

当线程1的称重器发出呼叫时,它将切换到主线程以访问产品信息,并完成任何其他工作,例如引发系统其他部分的事件。

当前,从线程1,2,... N到主线程的此切换是由本地解决方案完成的,以避免在整个代码库中锁定代码。这是用.Net 1.1编写的,并且自从迁移到dotnet核心3.1以来。我认为可能会有一个框架,库,工具,技术等可以为我们解决这个问题,或者只是更好的方法

这是我仍在学习的现有系统。希望这有道理。

解决方法

使用内存数据库是一种选择,只要您愿意将所有并发情况委派给数据库,并且不使用代码执行任何操作即可。例如,如果您必须根据某些条件更新数据库中的值,则应由数据库而不是您自己的代码来检查条件。

在各处添加锁也是一种选择,几乎可以肯定,它将很快导致难以维护的代码。这段代码中可能充斥着一开始隐藏的错误,这些错误通常会在最不幸的情况下随着时间的流逝一一发现。

您必须意识到自己正在解决一个难题,没有可用的魔术解决方案。在多线程应用程序中管理共享状态一直是痛苦的根源。

我的建议是将所有这些复杂性封装在线程安全的类中,以便应用程序的其余部分可以安全地调用。如何使这些类具有线程安全性取决于情况。

  1. 使用锁是最灵活的选项,但并非总是最有效的,因为它有可能引起争用。

  2. 使用线程安全集合,例如ConcurrentDictionary,灵活性较差,因为它们提供的线程安全保证仅限于内部状态的完整性。例如,如果您必须根据从另一个集合获得的条件来更新一个集合,则不能仅通过使用线程安全集合来使整个操作成为原子操作。另一方面,这些集合提供了比简单锁更好的性能。

  3. 使用immutable collections(例如ImmutableQueue)是另一个有趣的选择。与并发集合相比,它们在内存和CPU方面的效率都较低(在许多情况下,添加/删除是O(Log n)而不是O(1)),并且不比它们灵活得多,但是它们在提供快照方面非常有效主动处理的数据。要自动更新一个不可变的集合,可以使用方便的ImmutableInterlocked.Update方法。它使用同一集合的更新版本来更新不可变集合的引用,而无需使用锁。在与其他线程竞争的情况下,它可以多次调用提供的转换,直到赢得比赛为止。