十二、异步_实践

 一、线程实践步骤

建立数据表 提供两个版本Mysql和sqlserver 两种

create database Shop 
go
use Shop
go

create table Phone
(
	Id int primary key identity(1,1),
	Phone varchar(32),
	PCount int
)


insert into Phone values('vivo nex',3)

select * from Phone


create table User_X_Phone
(
	Id int primary key identity(1,1),
	PhoneId int,
	UserId varchar(64)
)


select * from Phone
select * from User_X_Phone

truncate table Phone
truncate table User_X_Phone
insert into Phone values('vivo nex',3)

  SqlServer版本的

create database Shop 
go
use Shop
go

CREATE TABLE Phone (
	Id int PRIMARY KEY AUTO_INCREMENT,
	Phone varchar(32),
	PCount int
)

INSERT INTO `Phone` (`Id`, `Phone`, `PCount`) VALUES (NULL, 'vivo nex', '3');

select * from Phone


create table User_X_Phone
(
	Id int PRIMARY KEY AUTO_INCREMENT,
	PhoneId int,
	UserId varchar(64)
)


select * from Phone
select * from User_X_Phone

truncate table Phone
truncate table User_X_Phone
insert into Phone values('vivo nex',3)

  MySql版本

二、项目创建 我们使用FrameWork构建的控制台项目操作。

添加链接字符串

  <connectionStrings>
    <add name="connString" connectionString="server=47.94.174.85;database=testDb;uid=testDb;password=testDb" providerName="System.Data.SqlClient" />
  </connectionStrings>

  链接代码操作

using System.Configuration;
using System.Data;
using System.Data.SqlClient;

namespace ConsoleApp1
{
    class Program
    {
       
        private static readonly string connectionString  = ConfigurationManager.ConnectionStrings["connString"].ConnectionString;
        static void Main(string[] args)
        {
            using (SqlConnection connection = new SqlConnection(connectionString))
            {
                if (connection.State == System.Data.ConnectionState.Closed)
                {
                    connection.Open();//执行连接
                }
                else
                {
                    SqlDataAdapter sqlDa = new SqlDataAdapter("SELECT * FROM `Phone`", connection);
                    DataTable dt = new DataTable();
                    sqlDa.Fill(dt);
                }
            }
        }
    }
}

  

下面是Mysql通过字符串链接操作

 

写代码

using MySql.Data.MySqlClient;
using System;
using System.Configuration;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;

namespace ConsoleApp1
{
    class Program
    {

        private static readonly string connectionString = ConfigurationManager.ConnectionStrings["connString"].ConnectionString;
        static void Main(string[] args)
        {
            try
            {
                using (MySqlConnection connection = new MySqlConnection(connectionString))      //MySqlClient 手动添加引用 using MySql.Data.MySqlClient;
                {
                    if (connection.State == System.Data.ConnectionState.Closed)
                    {
                        connection.Open();//执行连接
                    }
                    string sqlString = "SELECT * FROM `Phone`";
                    MySqlCommand cmd = new MySqlCommand(sqlString, connection);
                    MySqlDataReader reader = cmd.ExecuteReader();//执行ExecuteReader()返回一个MySqlDataReader对象

                    while (reader.Read())
                    {
                        if (reader.HasRows)//有一行读一行
                        {
                            Console.Write((string)reader["Phone"]);
                        }
                    }
                    Console.ReadKey();
                }

            }
            catch (MySqlException ex)
            {

                switch (ex.Number)
                {
                    case 0:
                        Console.Write("Cannot connect to server.  Contact administrator");
                        break;

                    case 1045:
                        Console.Write("Invalid username/password, please try again");
                        break;
                }
            }
        }
    }
}

  

直接EF操作方式实践展示

实现基础服务

        static void Main(string[] args)
        {
            Model1 se = new Model1();
            //查询手机库存
            Phone phone = se.Phones.Where(a => a.Id == 1).FirstOrDefault();//表为复数
            if (phone.PCount < 1)
            {
                Console.WriteLine("秒杀已经结束");
            }
            else
            {
                //减去库存
                phone.PCount--;
                //添加一个抢成功的人
                User_X_Phone uxp = new User_X_Phone() { PhoneId = 1 };
                uxp.UserId = Guid.NewGuid().ToString();
                se.User_X_Phone.Add(uxp);
                se.SaveChanges();
                Console.WriteLine("恭喜抢购成功");
            }
            //程序实现基础的买购(买卖购物)服务
        }

  

开辟多线程方式-具体查看线程id

        static void Main(string[] args)
        {
            //方式一、开辟多线程方法
            //for (int i = 0; i <= 20; i++)
            //{
            //    Thread thread = new Thread(()=> {
            //        Console.WriteLine("线程运行");
            //    });
            //    thread.Start();
            //}
            //Console.WriteLine("主线程运行");
            //Console.ReadLine();
            //方式二、线程池方式开辟多线程
            //for (int i = 0; i < 10; i++)
            //{
            //    ThreadPool.QueueUserWorkItem(
            //        (a) =>
            //        {
            //            Console.WriteLine("线程运行" + a);
            //            Thread.Sleep(2000);//让线程延迟2秒执行一个
            //        }
            //     ,i);
            //}
            //Console.WriteLine("主线程运行");
            //Console.ReadLine();

            //方式三 Tasky异步方法,里面也是实现的多线程            
            for (int i = 0; i < 10; i++)
            {
                Task task = new Task((a) =>
                {
                    Console.WriteLine("线程运行" + a);
                }, i);
                task.Start();
            }
            Console.WriteLine("主线程运行");
            Console.ReadLine();

            /*
            //基础买购方法       
            Model1 se = new Model1();
            //查询手机库存
            Phone phone = se.Phones.Where(a => a.Id == 1).FirstOrDefault();//表为复数
            if (phone.PCount < 1)
            {
                Console.WriteLine("秒杀已经结束");
            }
            else
            {
                //减去库存
                phone.PCount--;
                //添加一个抢成功的人
                User_X_Phone uxp = new User_X_Phone() { PhoneId = 1 };
                uxp.UserId = Guid.NewGuid().ToString();
                se.User_X_Phone.Add(uxp);
                se.SaveChanges();
                Console.WriteLine("恭喜抢购成功");
            }
            //程序实现基础的买购(买卖购物)服务*/
        }

  

线程运行服务(出现的问题)

--多线程
--第一次情况我只有三个手机 循环抢购成功竟然有10个。
--第二次竟然有抢购成功8个人。
--第三次 抢购 手机还剩下一个 循环次数10人,成功10个 手机竟然还剩余1。

普通进程运行服务

 

解决线程出现的问题(异步加锁->异步队列==同步)

        static object varlock = new object();//一、静态变量
        static void Main(string[] args)
        {
            //实践
            for (int i = 0; i < 20; i++)
            {
                Task task = new Task((obj) =>//标识线程id
                {
                    Model1 se = new Model1();//在写代码时特别注意:同一个上下文实例,不在多个线程中使用
                    lock (varlock)   //二、加锁 仅是让异步方法内操作的时候去排队,仅仅与当前函数并行,从而不影响下行语句的执行,相当于异步处理
                    {
                        Phone phone = se.Phones.Where(a => a.Id == 1).FirstOrDefault();//表为复数 
                        //三、取数据有数量 有快,有慢-点在这个位置,主要语句-因异步等同于类似并行方式 线程异步问题主要出现在这个位置 每次查询后执行放在锁内
                        //比如6人还没到判断并且检查库存的时候,就读取到库存有3个,  然后判断到else去执行 剩下四个忙了 才到if里面
                        // 也可能 6人中有 2个人读取到有手机数是3个 另外两个读取到的时候手机是2个 最后两个人读取到的时候是1 (比较读取的时候,正时没有减去)
                        if (phone.PCount < 1)    
                        {
                            Console.WriteLine("秒杀已经结束");
                        }
                        else
                        {
                            //减去库存
                            phone.PCount--;
                            //添加一个抢成功的人
                            User_X_Phone uxp = new User_X_Phone() { PhoneId = 1 };
                            uxp.UserId = Guid.NewGuid().ToString();
                            se.User_X_Phone.Add(uxp);
                            se.SaveChanges();
                            Console.WriteLine("恭喜抢购成功");
                        }
                    }
                }, i);
                task.Start();
            }
            Console.WriteLine("主线程运行");
            Console.ReadLine();
        }
    }

  

异步内的局部锁

添加备注:3、创建不唯一,无论异步线程进入锁,其他异步必须等待。

    class Program
    {
        static object varlock = new object();//一、静态变量
        static void Main(string[] args)
        {
            for (int i = 0; i < 20; i++)
            {
                Task task = new Task((obj) =>//标识线程id
                {
                    Model1 se = new Model1();//在写代码时特别注意:同一个上下文实例,不在多个线程中使用
                    Phone phone;
                    lock (varlock)   //二、加锁 仅是让异步方法内操作的时候去排队,仅仅与当前函数并行,从而不影响下行语句的执行,相当于异步处理
                    {
                        phone = se.Phones.Where(a => a.Id == 1).FirstOrDefault();//表为复数                         
                        Console.WriteLine("异步查询的手机数量:" + phone.PCount + "--线程的ID" + obj);
                    }
                    if (phone.PCount < 1)
                    {
                        Console.WriteLine("秒杀已经结束");
                    }
                    else
                    {
                        Console.WriteLine("恭喜抢购成功");//我提示先放在前面检验结果
                        //第一个还没来及删减库存,就可以能已经有几个异步陆续进去else里面,
                        //1、因锁而让异步线程陆续去执行,导致第二个或者第三个异步获取未改变数量的时候执行到else里。
                        //即锁释放了,创建运行第二个异步查询过程中比第一个异步处理过程中快,也可能第三个异步创建运行,第一个异步处理还没有处理完,毕竟lock的处理低代码量少。                                                  
                        phone.PCount--; //减去库存                        
                        User_X_Phone uxp = new User_X_Phone() { PhoneId = 1 };//添加一个抢成功的人
                        uxp.UserId = Guid.NewGuid().ToString();
                        se.User_X_Phone.Add(uxp);
                        se.SaveChanges();
                    }
                }, i);
                task.Start();
            }
            Console.WriteLine("主线程运行");
            Console.ReadLine();
        }
    }

  

原文地址:https://www.cnblogs.com/fger/p/10783567.html