2012-02-08 2 views
4

У нас есть система регистрационного типа, которая отправляет подтверждение по электронной почте после завершения. В течение нескольких минут система имела около 3000 регистраций, и мы заметили ошибку. Если пользователь A регистрирует несколько мс после регистрации пользователем B, пользователь A получит данные пользователя B по электронной почте. Нам удалось решить проблему, и я сузил ее до этого фрагмента кода, который получает шаблон электронной почты из кеша, и просто заменяет строку на держателе места.Weird 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; 
} 

И я не могу понять, за жизнь мне, почему это исправили проблему. Может ли кто-нибудь пролить свет на это?

EDIT: Вот мой метод 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' ... Скорее всего, он предоставляет экземпляры * shared * среди нескольких потоков, поэтому вы создаете условие гонки при изменении' content.Text'. –

+0

Недостаточно информации; каков фактический тип 'content'?Как реализуется метод GetContent? Вы всегда раздаете один и тот же экземпляр 'content'? Поддерживает ли этот код многопоточность? В этом случае это может быть просто условие гонки, и вам необходимо обеспечить правильную синхронизацию потоков. –

+0

Я отредактировал свой вопрос. – eth0

ответ

3

В первый раз вы замените тег "@@[email protected]@" на номер context.Text с деталями первого пользователя. Как только вы это сделаете, он снова не возвращается к "@@[email protected]@", поэтому каждый получает информацию об этом, пока ваш кеш не будет сброшен. Вы должны избегать модифицирующих объектов, которые вы получаете из кэша:

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

Я отредактировал свой вопрос. – eth0

+0

@ eth0 Ничего не изменилось: ваш код изменяет общий экземпляр, который возвращается каждому запросу, который вызывает 'CacheHelper' в течение короткого промежутка времени, определенного через' WebConfig.GetCacheDuration (CacheProfile.Short) ', а затем кешированное значение предположительно, будет сброшен. Если вы увеличите продолжительность кэширования, больше пользователей увидит содержимое первого пользователя, отправленного им. – dasblinkenlight

+0

Я думаю, это имеет смысл. У меня был тайм-аут тайм-аута, равный 60 секундам, но мы не видели двух регистраций более чем на 6 секунд. Любая идея, почему это может быть? – eth0

1

Трудно сказать, не зная, как работает ваш метод CacheHelper, или какой тип content есть. Но кажется, что вместо того, чтобы возвращать строку, она возвращает объект Content какого-либо типа по ссылке. Таким образом, если два потока запускаются одновременно, оба могут использовать один и тот же объект содержимого, возвращаемый CacheHelper.

Предполагая, что CacheHelper отвечает за создание абсолютно нового шаблона контента каждый раз, когда вы его вызываете, ваш исходный код является ошибкой в ​​том смысле, что каждый вызов Replace изменяет TEMPLATE, а не строку, полученную из него.

Я предполагаю, что этот код будет работать:

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

Важный бит не читает текст от содержания в в локальной переменной, это не замена текста свойства Контента, которая, по-видимому общий.

2

Проблема заключается в том, что ваш content объект является общим объектом между всеми, кто обращается к нему. Используя первый подход content.Text = context.Text.Replace(), вы собираетесь мутировать текст для всех, кто одновременно обращается к нему.

В вашем втором подходе вы не мутируете текст внутри общего объекта, поэтому каждый получает свой собственный текст одновременно. Чтобы избежать этой проблемы в будущем, вы должны подумать о том, чтобы сделать ваше свойство content.Text доступным только для чтения потребителям (позволяя устанавливать текст только в конструкторе или выдавать только интерфейсы с доступом только для чтения). Таким образом, избежать этой ошибки даже во время компиляции.

Смежные вопросы