在异步上下文中添加到字典时出现 NullReferenceException
NullReferenceException when adding to Dictionary in Asynchronous context
我的行为很奇怪,如图所示:
正如您在 Watch window 中看到的那样,所有可能 null
都不是 null
。
函数的完整代码如下:
public LogInTokenCache GetUserIdFromToken(string token)
{
LogInTokenCache item = null;
if (this.TokenCache.ContainsKey(token))
{
item = this.TokenCache[token];
if (item.ExpirationTime < DateTime.Now)
{
this.TokenCache.Remove(item.Token);
return null;
}
}
else
{
LogInTokenBusiness tokenBusiness = new LogInTokenBusiness();
var entity = tokenBusiness.FindToken(token);
if (entity != null && entity.Token != null)
{
item = new LogInTokenCache()
{
Token = entity.Token,
UserID = entity.UserId,
ExpirationTime = entity.ExpirationTime,
};
this.TokenCache.Add(item.Token, item);
}
}
return item;
}
我使用了“查找所有引用”功能,这是我唯一使用构造函数和声明的地方(我将它用于整个 Web 应用程序):
public class IdentityController : Controller
{
private static EmailAdpater EmailAdapter = new EmailAdpater();
private static UserIdentityTokenShortener TokenShortener = new UserIdentityTokenShortener();
public static LoginTokenManager LoginTokenManager = new LoginTokenManager();
...
有人遇到过这个问题吗?我做错了什么?
编辑:添加了 StackTrace 和详细信息
EDIT2:已编辑标题,以便将来遇到相同问题的人可以搜索此主题。
at System.Collections.Generic.Dictionary12.Insert(TKey key, TValue value, Boolean add)
at System.Collections.Generic.Dictionary12.Add(TKey key, TValue value)
at MobileDatingAPI.Models.LoginTokenManager.GetUserIdFromToken(String token) in d:\FPT University\Capstone 2\TFSRepo\Projects\MobileDatingAPI\MobileDatingAPI\Models\LoginTokenManager.cs:line 48
at MobileDatingAPI.Models.Utils.GetUserFromTokenID(Controller controller, String token, BaseApiViewModels model) in d:\FPT University\Capstone 2\TFSRepo\Projects\MobileDatingAPI\MobileDatingAPI\Models\Utils\Utils.cs:line 68
at MobileDatingAPI.Controllers.CommunityController.UpdateActivity(String token, Nullable11 longitude, Nullable11 latitude) in d:\FPTUniversity\Capstone 2\TFSRepo\Projects\MobileDatingAPI\MobileDatingAPI\Controllers\CommunityController.cs:line 84
at lambda_method(Closure , ControllerBase , Object[] )
at System.Web.Mvc.ActionMethodDispatcher.Execute(ControllerBase controller, Object[] parameters)
at System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary12 parameters)
at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary12 parameters)
at System.Web.Mvc.Async.AsyncControllerActionInvoker.ActionInvocation.InvokeSynchronousActionMethod()
at System.Web.Mvc.Async.AsyncControllerActionInvoker.<BeginInvokeSynchronousActionMethod>b__39(IAsyncResult asyncResult, ActionInvocation innerInvokeState)
at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult12.CallEndDelegate(IAsyncResult asyncResult)
at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResultBase`1.End()
at System.Web.Mvc.Async.AsyncResultWrapper.End[TResult](IAsyncResult asyncResult, Object tag)
at System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethod(IAsyncResult asyncResult)
at System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.<InvokeActionMethodFilterAsynchronouslyRecursive>b__3d()
at System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.<>c__DisplayClass46.<InvokeActionMethodFilterAsynchronouslyRecursive>b__3f()
I use Multi-Threading
这几乎肯定是问题所在:因为您代码中的所有内容都是 null
检查 and/or 使用非 null
的值创建的,唯一出现问题的地方可能发生的是 Dictionary
.
的实施
当达到字典的容量时,集合会重新分配其内部数据结构。如果一个线程在另一个线程执行的重新分配过程中捕获字典,则桶内的某些变量将未初始化,即 null
或默认值,具体取决于类型。由于这不是桶的通常状态,字典将尝试取消引用它,从而导致空指针异常。
为了避免这个问题,在Add
的调用周围添加同步:
// Add this declaration where you declare TokenCache dictionary
object TokenCacheLock = new object();
...
lock (TokenCacheLock) {
// Add a lock around your access of TokenCache
if (this.TokenCache.ContainsKey(token)) ...
}
请注意,由于对字典的添加与读取同时发生,因此对 TokenCache
的所有访问都需要同步。
一个更简单的解决方案是使用 ConcurrentDictionary<K,V>
.
public static LoginTokenManager LoginTokenManager = new LoginTokenManager();
(I use it for entire Web App):
这是你的问题。 Dictionary<TKey, TValue>
类型不是线程安全的。参见 Thread safety with Dictionary<int,int> in .Net。
我的行为很奇怪,如图所示:
正如您在 Watch window 中看到的那样,所有可能 null
都不是 null
。
函数的完整代码如下:
public LogInTokenCache GetUserIdFromToken(string token)
{
LogInTokenCache item = null;
if (this.TokenCache.ContainsKey(token))
{
item = this.TokenCache[token];
if (item.ExpirationTime < DateTime.Now)
{
this.TokenCache.Remove(item.Token);
return null;
}
}
else
{
LogInTokenBusiness tokenBusiness = new LogInTokenBusiness();
var entity = tokenBusiness.FindToken(token);
if (entity != null && entity.Token != null)
{
item = new LogInTokenCache()
{
Token = entity.Token,
UserID = entity.UserId,
ExpirationTime = entity.ExpirationTime,
};
this.TokenCache.Add(item.Token, item);
}
}
return item;
}
我使用了“查找所有引用”功能,这是我唯一使用构造函数和声明的地方(我将它用于整个 Web 应用程序):
public class IdentityController : Controller
{
private static EmailAdpater EmailAdapter = new EmailAdpater();
private static UserIdentityTokenShortener TokenShortener = new UserIdentityTokenShortener();
public static LoginTokenManager LoginTokenManager = new LoginTokenManager();
...
有人遇到过这个问题吗?我做错了什么?
编辑:添加了 StackTrace 和详细信息 EDIT2:已编辑标题,以便将来遇到相同问题的人可以搜索此主题。
at System.Collections.Generic.Dictionary12.Insert(TKey key, TValue value, Boolean add)
at System.Collections.Generic.Dictionary12.Add(TKey key, TValue value)
at MobileDatingAPI.Models.LoginTokenManager.GetUserIdFromToken(String token) in d:\FPT University\Capstone 2\TFSRepo\Projects\MobileDatingAPI\MobileDatingAPI\Models\LoginTokenManager.cs:line 48
at MobileDatingAPI.Models.Utils.GetUserFromTokenID(Controller controller, String token, BaseApiViewModels model) in d:\FPT University\Capstone 2\TFSRepo\Projects\MobileDatingAPI\MobileDatingAPI\Models\Utils\Utils.cs:line 68
at MobileDatingAPI.Controllers.CommunityController.UpdateActivity(String token, Nullable11 longitude, Nullable11 latitude) in d:\FPTUniversity\Capstone 2\TFSRepo\Projects\MobileDatingAPI\MobileDatingAPI\Controllers\CommunityController.cs:line 84
at lambda_method(Closure , ControllerBase , Object[] )
at System.Web.Mvc.ActionMethodDispatcher.Execute(ControllerBase controller, Object[] parameters)
at System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary12 parameters)
at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary12 parameters)
at System.Web.Mvc.Async.AsyncControllerActionInvoker.ActionInvocation.InvokeSynchronousActionMethod()
at System.Web.Mvc.Async.AsyncControllerActionInvoker.<BeginInvokeSynchronousActionMethod>b__39(IAsyncResult asyncResult, ActionInvocation innerInvokeState)
at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult12.CallEndDelegate(IAsyncResult asyncResult)
at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResultBase`1.End()
at System.Web.Mvc.Async.AsyncResultWrapper.End[TResult](IAsyncResult asyncResult, Object tag)
at System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethod(IAsyncResult asyncResult)
at System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.<InvokeActionMethodFilterAsynchronouslyRecursive>b__3d()
at System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.<>c__DisplayClass46.<InvokeActionMethodFilterAsynchronouslyRecursive>b__3f()
I use Multi-Threading
这几乎肯定是问题所在:因为您代码中的所有内容都是 null
检查 and/or 使用非 null
的值创建的,唯一出现问题的地方可能发生的是 Dictionary
.
当达到字典的容量时,集合会重新分配其内部数据结构。如果一个线程在另一个线程执行的重新分配过程中捕获字典,则桶内的某些变量将未初始化,即 null
或默认值,具体取决于类型。由于这不是桶的通常状态,字典将尝试取消引用它,从而导致空指针异常。
为了避免这个问题,在Add
的调用周围添加同步:
// Add this declaration where you declare TokenCache dictionary
object TokenCacheLock = new object();
...
lock (TokenCacheLock) {
// Add a lock around your access of TokenCache
if (this.TokenCache.ContainsKey(token)) ...
}
请注意,由于对字典的添加与读取同时发生,因此对 TokenCache
的所有访问都需要同步。
一个更简单的解决方案是使用 ConcurrentDictionary<K,V>
.
public static LoginTokenManager LoginTokenManager = new LoginTokenManager();
(I use it for entire Web App):
这是你的问题。 Dictionary<TKey, TValue>
类型不是线程安全的。参见 Thread safety with Dictionary<int,int> in .Net。