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

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条)

报告相同问题?

悬赏问题

  • ¥50 易语言把MYSQL数据库中的数据添加至组合框
  • ¥20 求数据集和代码#有偿答复
  • ¥15 关于下拉菜单选项关联的问题
  • ¥20 java-OJ-健康体检
  • ¥15 rs485的上拉下拉,不会对a-b<-200mv有影响吗,就是接受时,对判断逻辑0有影响吗
  • ¥15 使用phpstudy在云服务器上搭建个人网站
  • ¥15 应该如何判断含间隙的曲柄摇杆机构,轴与轴承是否发生了碰撞?
  • ¥15 vue3+express部署到nginx
  • ¥20 搭建pt1000三线制高精度测温电路
  • ¥15 使用Jdk8自带的算法,和Jdk11自带的加密结果会一样吗,不一样的话有什么解决方案,Jdk不能升级的情况