我今天在我们的系统中发现了一个错误,但我不明白为什么会发生。我要求解释EF6和C#在这里的工作方式,因为我认为我缺少了一些非常重要的东西。
我的问题是“这里正在发生什么”,而不是“如何使其工作”。修复非常简单-我可以添加一些锁来防止出现竞争状况,但是我只是不知道这怎么可能。
我在使用EF6的C#中有ASP.NET Web应用程序。设计非常简单-我们有用户和任务。用户拥有自己的余额,如果余额不为零,则可以启动任务。任务是异步执行的,任务完成后,应用程序将接收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函数是运行数据库事务的函数,如果用户余额为非零,并且成功从余额中减去1并将其保存到数据库,则该函数返回true。如果有任何问题或余额为零,则该函数返回false。
我们将工作单元概念与通用存储库一起使用,因此Get方法看起来很像这里的方法:http : //www.asp.net/mvc/overview/older-versions/getting-started-with-ef- 5使用MVC-4 /在ASP.NET MVC应用程序中实现存储库和工作单元模式
同样,可以在本文中找到Update和Save方法。
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构造函数仅构建一个结构以从任务的数据发送给用户,这在这里不是重要的功能。重要的事实是,当将新任务放入数据库时,会使用Finished设置为false并将Paid设置为false对其进行初始化。我们将Finished设置为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如何看到task.Finished == true,同时看到task.Paid == false?
线程2在方法启动之后但尚未完成之前询问Succeeded
andPaid
属性Save
。在这一点上,对于从存储库中检索到的任务,很有可能(确实如此)Succeeded
是正确的,Paid
也可能是错误的。
回调需要是事务性的,以便任务完成后,其他线程将无法访问它,直到对其进行更新。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句