2012-02-08 60 views
4

当我们有一个发送确认电子邮件一旦完成注册类型的系统。系统在几分钟内有大约3000个注册,我们注意到一个错误。如果用户A在用户B注册后注册了几ms,用户A将通过电子邮件获取用户B的详细信息。我们确实设法解决了这个问题,并将其缩小到了从缓存中获取电子邮件模板的这段代码,只是在占位符上替换了一个字符串。奇怪C#错误替换文本字符串

private string ProcessEmailBody(MyRegistrationModel registration) 
{ 
    var content = CacheHelper.GetContent("REGISTRATIONEMAIL"); 

    if (content != null) 
    { 
     content.Text = context.Text.Replace("@@[email protected]@", registration.FullName); 

     return content.Text; 
    } 
    else return null; 
} 

CacheHelper.GetContent()方法是静态的,我做这个固定的这个“错误”:

private string ProcessEmailBody(MyRegistrationModel registration) 
{ 
    var content = CacheHelper.GetContent("REGISTRATIONEMAIL"); 

    if (content != null) 
    { 
     string body = content.Text; 
     body = body.Replace("@@[email protected]@", registration.FullName); 

     return body; 
    } 
    else return null; 
} 

我想不出我的生活为什么这个有固定的问题。任何人都可以解释这一点吗?

编辑:这是我的getContent()方法(我知道的签名比上面不同,我被短暂)

public static Content GetContent(string key, int partnerSiteId, int? version, IContentRepository contentRepository, out string cacheKey) 
{ 
    cacheKey = string.Format("{0}_{1}_{2}", key, partnerSiteId, version); 

    var content = CacheManager.Get(cacheKey,() => contentRepository.GetContent(key, partnerSiteId, version), WebConfig.GetCacheDuration(CacheProfile.Short)); 

    return content; 
} 

private static DataCache _Cache = null; // DataCache is from AppFabric (Microsoft.ApplicationServer.Caching) 

public static T Get<T>(string objectKey, Func<T> reloadItemExpresion, TimeSpan cacheDuration) where T : class 
{ 
    if (_Cache == null) 
    { 
     if (reloadItemExpresion != null) 
     { 
      return reloadItemExpresion.Invoke(); 
     } 

     return null; 
    } 

    object cachedObject = null; 

    try 
    { 
     cachedObject = _Cache.Get(objectKey); 
    } 
    catch (Exception ex) 
    { 
     if (ex is FileNotFoundException) 
     { 
      _Cache.Remove(objectKey); 
     } 
    } 

    if (cachedObject != null) 
    { 
     return cachedObject as T; 
    } 

    if (reloadItemExpresion != null && cacheDuration > TimeSpan.Zero) 
    { 
     T item = reloadItemExpresion.Invoke(); 

     if (item != null) 
     { 
      Insert(item, objectKey, cacheDuration); 
     } 

     return item; 
    } 

    return null; 
} 

contentRepository.GetContent刚刚熄灭到数据库并获取实际的内容了。

+3

显示'CacheHelper.GetContent'的代码...很可能它提供了多个线程间的实例*共享*,因此您在修改'content.Text'时创建了竞争条件。 – 2012-02-08 11:07:29

+0

没有足够的信息; “内容”的实际类型是什么?GetContent方法是如何实现的?你总是发出同样的“内容”实例吗?此代码是否运行多线程?在这种情况下,这可能只是一个竞争条件,您需要确保正确的线程同步。 – 2012-02-08 11:11:58

+0

我编辑了我的问题。 – eth0 2012-02-08 11:24:31

回答

3

第一次通过与第一个用户的详细信息替换context.Text"@@[email protected]@"标签。一旦你这样做了,它永远不会再回到"@@[email protected]@",所以每个人都会得到那个家伙的细节,直到你的缓存被重置。你应该避免你从缓存中得到修改对象:

private string ProcessEmailBody(MyRegistrationModel registration) { 
    var content = CacheHelper.GetContent("REGISTRATIONEMAIL"); 
    return content != null ? content.Replace("@@[email protected]@", registration.FullName) : null; 
} 
+0

我编辑了我的问题。 – eth0 2012-02-08 11:25:51

+0

@ eth0没有任何改变,真的:你的代码修改了一个共享实例,它返回给每个在通过'WebConfig.GetCacheDuration(CacheProfile.Short)'定义的短时间间隔内调用'CacheHelper'的请求,然后缓存值大概会被重置。如果让缓存持续时间更长,更多用户会看到第一个用户发送给他们的内容。 – dasblinkenlight 2012-02-08 11:31:32

+0

我想这是有道理的。我将缓存超时设置为60秒,但我们从未见过两次注册超过6秒。任何想法为什么这可能是? – eth0 2012-02-08 16:49:07

1

很难不知道说你CacheHelper方法是如何工作的,或content是什么类型。但似乎不是返回一个字符串,而是通过引用返回某种内容对象。因此,如果两个线程同时运行,两者都可能使用由CacheHelper返回的相同内容对象。

假设CacheHelper负责在每次调用它时不创建全新的内容模板,那么您的原始代码就是错误的,因为每个Replace调用都会更改TEMPLATE,而不是从其派生的字符串。

我猜这个代码也将工作:

string body = content.Text.Replace("@@[email protected]@", registration.FullName); 
return body; 

最重要的一点是不读内容的文本到一个局部变量,它不会取代内容的Text属性,这显然是共享的。

2

的问题是,你的content对象是每个人之间的共享对象谁访问它。通过使用第一种方法content.Text = context.Text.Replace(),您将为正在同时访问它的所有人改变文本。

在第二种方法中,您不要改变共享对象中的文本,因此每个人都可以同时获取自己的文本。为了避免将来出现这个问题,你应该考虑使你的content.Text属性只读给消费者(只允许在构造函数中设置文本,或者只给出具有只读访问权限的接口)。因此即使在编译时也能避免这个bug。