两个线程访问同一对象会产生意外结果

瓦帕克

我今天在我们的系统中发现了一个错误,但我不明白为什么会发生。我要求解释EF6和C#在这里的工作方式,因为我认为我缺少了一些非常重要的东西。

我的问题是“这里正在发生什么”,而不是“如何使其工作”。修复非常简单-我可以添加一些锁来防止出现竞争状况,但是我只是不知道这怎么可能。

我在使用EF6的C#中有A​​SP.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?

理查德·艾恩斯(Richard Irons)

线程2方法启动之后但尚未完成之前询问SucceededandPaid属性Save在这一点上,对于从存储库中检索到的任务,很有可能(确实如此)Succeeded是正确的,Paid也可能是错误的。

回调需要是事务性的,以便任务完成后,其他线程将无法访问它,直到对其进行更新。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

如何检查两个变量是否在Python中引用了同一对象?

比较两个变量是否在python中引用了同一对象

Java-创建对同一对象的两个引用

防止同一对象与休眠中的两个不同会话相关联

同一对象的两个不同的同步方法?

===当两个引用都应指向同一对象时,在Nashorn中返回false

如何确定两个Win32 API句柄代表同一对象?

从两个模块中的同一模块导入同一对象:

EF代码首先,指向同一对象的两个导航属性

Python:两个变量何时指向内存中的同一对象?

如何检查两个变量是否指向内存中的同一对象?

在同时运行的两个函数中访问同一对象的属性

合并两个PySpark DataFrame会产生意外结果

Python:复制变量或使变量中的两个指向同一对象之间的区别

如何检查两个引用变量是否借用了同一对象?

在管道中的同一对象上调用两个不同的函数(%>%)

缩放同一对象的两个网格

状态机图-同一对象的两个状态

两个名称指向python中的同一对象

如何将两个纹理采样到同一对象上?

测试两个迭代器是否来自同一对象

从两个线程写入一个文件会产生意外结果

比较两个NSDate对象的相等性会产生意外结果

Qt在两个不同的线程中运行同一对象的2个方法

java多线程两个线程尝试同时从同一对象输入流中读取object()

在同一模型中注释会产生意外结果

C Linux pthread:使用消息队列将数据从一个线程发送到另一个线程会产生意外结果

WPF中同一对象上的两个动画同时进行

在 cosmos db 中处理同一对象上的两个 upsert