白日|梦想家 2021-06-16 11:05 采纳率: 50%
浏览 61
已采纳

EF访问Sqlite的多线程问题

环境: winform项目,使用EF6访问Sqlite,CodeFirst模式。所有组件都是直接在VS的Nuget管理器中下载的。

问题:我的一张Users表里有3个用户:张三、李四、王五,每个用户都拥有初始积分1000。我开启两个线程,线程内各自创建自己的dbcontext,然后各自去修改3个用户的积分。第一个线程是对每个用户积分都加100,第二个线程是对“张三”这一个玩家的积分减50。期望得到的结果应该是张三比其它玩家的积分少50,但实际执行时,常常出现,张三的积分是950,其它用户是1100,相差了150。这里应该是第二个线程后执行,且覆盖了第一个线程的写操作。怎么会出现这种情况呢?按说Sqlite作为数据库,不能保证这种基本的原子性吗?

代码:

访问Sqlite的DbContext类

    public class SqliteContext : DbContext
    {
        public DbSet<User> Users { get; set; }

        public SqliteContext() : base("SQLiteConnect")
        {
        }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            var init = new SqliteCreateDatabaseIfNotExists<SqliteContext>(modelBuilder);
            Database.SetInitializer(init);
        }
    }

添加初始数据:

        private void Form1_Load(object sender, EventArgs e)
        {
            //添加初始用户
            List<User> userList = new List<User>();
            userList.Add(new User() { Name = "张三", Score = 1000 });
            userList.Add(new User() { Name = "李四", Score = 1000 });
            userList.Add(new User() { Name = "王五", Score = 1000 });


            using (var context = new SqliteContext())
            {
                foreach (var item in userList)
                {
                    var user = context.Users.Where(s => s.Name == item.Name).FirstOrDefault();
                    if (user != null)
                    {
                        continue;
                    }
                    else
                    {
                        context.Users.Add(item);
                    }
                }

                context.SaveChanges();
            }
        }

开启线程去修改用户积分:

        private void button1_Click(object sender, EventArgs e)
        {
            //执行加分操作
            Task.Factory.StartNew(ModifyAllUserScore);
            Task.Factory.StartNew(ModifySingleUserScore);
        }

        //为所有用户加100分
        private async Task ModifyAllUserScore()
        {
            using (var context = new SqliteContext())
            {
                //先等待1秒
                await Task.Delay(1000);
                foreach (var user in context.Users)
                {
                    user.Score += 100;
                }

                await context.SaveChangesAsync();
            }
        }

        //仅为“张三”减少50分
        private async Task ModifySingleUserScore()
        {
            using (var context = new SqliteContext())
            {
                //先等待1秒
                await Task.Delay(1000);
                var user = await context.Users.Where(s => s.Name == "张三").FirstOrDefaultAsync();
                if (user != null)
                {
                    user.Score -= 50;
                }
                await context.SaveChangesAsync();
            }
        }
  • 写回答

3条回答 默认 最新

  • CSDN专家-showbo 2021-06-16 11:27
    关注
    var user = await context.Users.Where(s => s.Name == "张三").FirstOrDefaultAsync();

    这句和更新+100顺序不可控,有可能读取数据到内存中的user对象积分为1000 ,线程1才执行+100操作,内存中张三积分是1000,这样是直接从1000积分操作,而不是更新后的1100。所以出现950的问题。2个context是不同的事务,数据肯定不会同步的。

    要么就改成执行sql语句,这样并发出现更新张三的可能性小一些,不需要读取数据库后生成user对象这些耗时操作

    context.Database.ExecuteSqlCommand("update users set socre-=50 where name='张三'");

     

    object lokcer = new object();//申明一个全局变量

    需要ef操作数据库的时候lock下

     lock (lokcer) {
     //ef操作的代码
    }

    linq to sql有并发冲突检查,如果实体对象数据和读取数据库时的不一致会报错。。但是这个应该不是你想要的效果。

     

    你的要求感觉没有哪个框架会实现,因为不会更新前还会再读一次数据库更新实体对象。而且就算再次读的时候不一定就是最新的

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(2条)

报告相同问题?

悬赏问题

  • ¥15 如果要做一个老年人平板有哪些需求
  • ¥15 k8s生产配置推荐配置及部署方案
  • ¥15 matlab提取运动物体的坐标
  • ¥15 人大金仓下载,有人知道怎么解决吗
  • ¥15 一个小问题,本人刚入门,哪位可以help
  • ¥15 python安卓开发
  • ¥15 使用R语言GD包一直不出结果
  • ¥15 计算机微处理器与接口技术相关问题,求解答图片的这个问题,有多少个端口,端口地址和解答问题的方法和思路,不要AI作答
  • ¥15 如何根据一个截图编写对应的HTML代码
  • ¥15 stm32标准库的PID角度环