Словарь одновременно с Lazy Object - Является ли смешивание значений ключей проблемой многопоточности? - PullRequest
0 голосов
/ 30 марта 2020

Допустим, у меня этот ConcurrentDictionary статически определен на глобальном уровне (глобальный называется «WebApiApplication». Любая ссылка на WebApiApplication в следующих фрагментах ссылается на глобально определенные переменные stati c).

Public Shared Property ssMWMultiTenant As New ConcurrentDictionary(Of String, Lazy(Of MWManager))

И рассмотрим предварительный просмотр MWManager -

Public Class MWManager
    Public Property MW As MWAPI
    Public Property LastUsed As DateTime

    Private _copyLock As Object = Nothing

    Private Function GetLock() As Object
        If _copyLock IsNot Nothing Then Return _copyLock
        Dim newLock As Object = New Object()
        Dim prevLock As Object = Interlocked.CompareExchange(_copyLock, newLock, Nothing)
        Return If((prevLock Is Nothing), newLock, prevLock)
    End Function

    Public Sub New()

    End Sub

    Public Sub New(ByVal inMW As MWAPI)
        SyncLock GetLock()
            MW = inMW
            LastUsed = DateTime.Now
        End SyncLock
    End Sub

    Public Function Copy() As MWManager
        SyncLock GetLock()
            Dim copied As New MWManager()
            copied.MW = MW.Clone()
            copied.LastUsed = LastUsed
            Return copied
        End SyncLock
    End Function
End Class

И предварительный просмотр ManagerUtility

Public Class ManagerUtility

Public Shared Function GetMW(ByVal inForceLoad As Boolean, Optional ByVal inTenant As String = "") As MWAPI
    If WebApiApplication.MultiTenant = False Then
        Return WebApiApplication.StaticMW
    Else
        Dim ssMW As MW = GetMWMultiTenant(inTenant)

        Return ssMW
    End If
End Function

Public Shared Function GetMWMultiTenant(ByVal inForceLoad As Boolean, ByVal inTenant As String) As MWAPI
    Try
        If String.IsNullOrEmpty(inTenant) Then
            Return Nothing
        End If

        inTenant = inTenant.ToLower()

        Dim serialKey As String = ""

        'Some logic. 2 external webservice calls required to get the serialKey. they run in a linear manner'
         serialKey = webServiceResult.Rows(0)("SerialNumber")

        Return ExtendedAddOrUpdate(serialKey).Value.MW

    Catch ex As Exception
        'some error handling logic'
        Return Nothing
    Finally
        GC.Collect()
    End Try
End Function

Public Shared Function ExtendedAddOrUpdate(ByVal serialKey As String, ByVal inForceLoad As Boolean) As Lazy(Of MWManager)
    Return WebApiApplication.ssMWMultiTenant.AddOrUpdate(serialKey,
        Function(key As String)
            Return AddNewTenantMW(serialKey)
        End Function,
        Function(key As String, obj1 As Lazy(Of MWManager))
            Return UpdateTenantMW(serialKey, obj1)
        End Function)
End Function

Public Shared Function AddNewTenantMW(ByVal serialKey As String, ByVal inForceLoad As Boolean) As Lazy(Of MWManager)
    Return New Lazy(Of MWManager)(Function()
      Dim ssMWAPI As MWAPI = New MWAPI(ExternalAPI.MWAPI.LicenseUsageType.CLOUD, GetAppSetting("Path", GetType(String), False), GetAppSetting("WorkingPath", GetType(String), False), True)
      ssMWAPI._DEBUG_MODE = GetAppSetting("DebugMode", GetType(Boolean), False)
      ssMWAPI.SetCloudConnection("", serialKey)
      ssMWAPI.SetWorkingPath(IO.Path.Combine(GetAppSetting("workingPath", GetType(String), False), ssMWAPI._SerialNumber))
      ssMWAPI.LoadSettings(True, New TimeSpan, False,,,,, inForceLoad)

      If ssMWAPI._NeedsActivationNow = True Then
          Dim LicensingAPI As New ExternalAPI.LicensingAPI(ssMWAPI)

          LicensingAPI.ActivateLicense(ssMWAPI._SerialNumber, authKey)
      End If

      Return New MWManager(ssMWAPI)
  End Function, LazyThreadSafetyMode.ExecutionAndPublication)
End Function

Public Shared Function UpdateTenantMW(ByVal inSerialKey As String, ByVal inOldMWManager As Lazy(Of MWManager), ByVal inLoadSettings As Boolean, ByVal inForceLoad As Boolean) As Lazy(Of MWManager)
    Return New Lazy(Of MWManager)(Function()
      Dim newMWManager As New MWManager()
      newMWManager = inOldMWManager.Value.Copy()

      If inLoadSettings Then
          newMWManager.MW.LoadSettings(True, New TimeSpan, False,,,,, inForceLoad)
          newMWManager.SettingsLastRefreshed = DateTime.Now
      End If

      Return newMWManager
  End Function, LazyThreadSafetyMode.ExecutionAndPublication)
End Function

End Class

Это многопользовательская среда. Методы AddNewTenantMW и UpdateTenantMW предназначены для создания или обновления экземпляров; внутренне методы экземпляра MWAPI, такие как SetCloudConnection и LoadSettings, обмениваются данными с базами данных разных арендаторов. Параллельный словарь предназначен для хранения экземпляра MWAPI для каждого арендатора. Ключ относится к serialKey арендатора; значение относится к экземпляру MWAPI арендатора. Каждый метод API вызывает GetMW в самом начале и извлекает экземпляр MWAPI, который ссылается на клиента, частью которого является пользователь. С тех пор логика c в каждом методе API основывается главным образом на свойствах / методах, вызываемых возвращенным экземпляром MWAPI. «TenantID» шифруется и передается в запросе заголовка HTTP, который отправляется клиентом переднего плана в API.

If WebApiApplication.MultiTenant = True Then
    tenant = GetTenantFromHeader(Me.Request)
End If

Dim mwInst As MWAPI = MWManagerUtility.GetMainWindow(False, tenant)

Logi c хорошо работает с пользователями из одного запроса на отправку от арендатора. Это также хорошо работает, если подключены пользователи из двух арендаторов; до тех пор, пока звонки не отправляются параллельно. Когда в API отправляются параллельные запросы от разных арендаторов, внезапно пользователи из арендатора A в конечном итоге просматривают данные арендатора B; поскольку две пары ключ-значение будут указывать на экземпляр арендатора A (или экземпляр арендатора B).

Аспект параллелизма заставляет меня думать, что это определенно была проблема с многопоточностью; особенно с учетом того, что это WebApi, и каждый метод контроллера запускается в отдельном потоке. Параллельный словарь с отложенной загрузкой был моей попыткой обойти это. Тем не менее, у меня все еще есть та же проблема. Либо моя реализация параллелизма (предположительно поточно-ориентированная) не является правильной, либо моя проблема не была связана с многопоточностью. Любые советы?

...