Я нашел ошибку в нашей системе сегодня, но я не могу понять, почему это происходит. Я прошу объяснить, как здесь работают EF6 и C#, потому что я думаю, что у меня что-то очень важное.Два потока, обращающихся к одному и тому же объекту, создают неожиданный результат
Мой вопрос: «Что здесь происходит», а не «Как заставить его работать». Исправление довольно тривиально - я могу добавить некоторые блокировки, чтобы предотвратить состояние гонки, но я просто не понимаю, как это возможно.
У меня есть веб-приложение ASP.NET в C# с использованием EF6. Дизайн довольно прост - у нас есть пользователи и задачи. Пользователи имеют свой баланс, и если он не равен нулю, они могут запускать задачи. Задачи выполняются асинхронно, и приложение получает обратный вызов HTTP POST с результатами после завершения задачи. Мы разрешаем пользователю активно спрашивать нашу систему о том, завершена ли задача. Если он еще не закончен, мы сообщим пользователю, что он не закончен, если он закончен, мы дадим ему результат.
Может случиться так, что задача не удалась, и в этом случае мы не хотим считать ее балансом пользователя. Вот почему баланс изменяется после того, как мы получим результаты задачи через обратный вызов и только если задача выполнена успешно.
Итак, вот сокращенный код отрезала от рутинной обработки обратного вызова, отображаются только соответствующие строки:
// before this we have extracted taskId and data from the call parameters
// log Callback: Data received
Task task = unitOfWork.TaskRepository.Get(t => t.TaskId == taskId).SingleOrDefault();
if (task != null)
{
task.Finished = true;
task.Succeeded = success;
task.Data = data;
if (task.Succeeded)
{
// log Callback: BillUser
if (BillUser(task.UserId))
{
// log Callback: BillUser succeeded
task.Paid = true;
}
else
{
// log Callback: BillUser failed
task.Paid = false;
}
}
else
{
// log Callback: Task failed
task.Paid = true;
}
unitOfWork.TaskRepository.Update(task);
// log Callback: Saving
unitOfWork.Save();
// log Callback: Saved
// log Callback: End
}
Это очень простой код. Функция BillUser - это функция, выполняющая транзакцию БД, и функция возвращает true, если баланс пользователя не равен нулю, и если ему удалось уменьшить 1 из баланса и сохранить его в БД. Если есть какая-либо проблема или баланс равен нулю, функция возвращает false.
Мы используем единицу концепции работы с родовым хранилищем, поэтому Get метод выглядит так, как в здесь: http://www.asp.net/mvc/overview/older-versions/getting-started-with-ef-5-using-mvc-4/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application
Аналогично методу обновления и сохранения можно найти в этой статье.
UnitOfWork принадлежит к контроллеру и инициализируется следующим образом:
public class TaskController : Controller
{
private UnitOfWork unitOfWork = new UnitOfWork();
этот контроллер реализует оба - метод обработки обратного вызова, из которого выше код, а также второй соответствующий метод, который обрабатывает пользователь запросы о том, завершена ли задача. Этот метод выглядит следующим образом:
public ActionResult GetResult(string taskId)
{
// log GetResult: Start
Task task = unitOfWork.TaskRepository.Get(t => t.TaskId == taskId).SingleOrDefault();
TaskResponse response = null;
if (task != null)
{
if (task.Finished)
{
response = new TaskResponse(task);
if (response.Succeeded)
{
// log GetResult: Task succeeded
if (task.Paid)
{
// log GetResult: Task paid
}
else
{
// log GetResult: Task unpaid
}
}
else
{
// log GetResult: Task failed
}
}
else
{
// log GetResult: Task not finished
}
}
// log GetResult: End
TaskResponse конструктор только создает структуру, чтобы отправить пользователь из данных Целевых, это не является важной функция здесь. Важным фактом является то, что когда новая задача помещается в БД, она инициализируется с параметром «Готово» равным false, а Paid - false. Единственное место, где мы установили Finished to true, находится в коде выше.
В чем проблема?
Большую часть времени приложение работает, но сегодня, я нашел эту последовательность в журнале:
thread 1: Callback: Data received
thread 1: Callback: BillUser
thread 2: GetResult: Start
thread 1: Callback: BillUser succeeded
thread 1: Callback: Saving
thread 2: GetResult: Task succeeded
thread 2: GetResult: Task unpaid
thread 1: Callback: Saved
thread 1: Callback: End
thread 2: GetResult: End
Баланс пользователя был отличен от нуля в любой момент времени. Из того, что я думал о том, как работает EF, это не должно быть возможным.
Может кто-нибудь объяснить, что произошло? Как может поток 2 увидеть задачу. Исправлено == true и в то же время task.Paid == false?
Не уверен в вашей конкретной проблеме, но если вы не делаете больше в своей процедуре обратного вызова, почему вы сохраняете каждый раз. Как и в этом, вы чувствуете, что спасаете, даже когда работа еще не закончена. Вы действительно это делаете. Кроме того, похоже, что вы всегда переопределены. Преуспеть перед тем, как проверить, что работа уже выполнена. Обратите внимание, что ничто из этого не помогает в вашей проблеме, просто наблюдая за показанным кодом. – TYY
Когда вызывается процедура обратного вызова, задача всегда завершается. Это может потерпеть неудачу или преуспеть, но это всегда закончено. Задача. Преемственность также правильно установлена, переменная успеха заполняется кодом, который не отображается. – Wapac