Допустим, у меня этот 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, и каждый метод контроллера запускается в отдельном потоке. Параллельный словарь с отложенной загрузкой был моей попыткой обойти это. Тем не менее, у меня все еще есть та же проблема. Либо моя реализация параллелизма (предположительно поточно-ориентированная) не является правильной, либо моя проблема не была связана с многопоточностью. Любые советы?