首页 新闻 会员 周边 捐助

c# async/await 前后线程的问题

0
悬赏园豆:10 [已解决问题] 解决于 2019-09-11 17:58

自定义异步方法:

public static async Task TransactionTestAsync()
        {
            Console.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId);
            int res = await AsyncTest(); //自定义的异步方法
            Console.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId);
        }

        public static async Task<int> AsyncTest()
        {
            return await Task.Run(() => {
                return 1;
            });
        }

打印出来的线程前后基本是不一致的

 

下面使用dapper的异步方法:

public static async Task TransactionTestAsync()
        {
            Console.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId);
            string sql1 = "INSERT INTO `person` (`Name`, `Sex`, `Age`) VALUES ('test', '1', '25');select @@IDENTITY;";
            int res = await MySqlHelper.ExecuteScalarAsync<int>(sql1); //调用Dapper里面的异步方法
            Console.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId);
        }

重复运行上面代码,当使用dapper的异步方法时打印出来的线程ID总是一样

 

按照我的理解来说使用await后面的代码都相当于是回调,那么应该使用与主线程不一样的线程,也就是前后线程不一致,那么像dapper提供的异步方法是怎么实现现在与主线程一样的呢?

猴哥aiyy的主页 猴哥aiyy | 初学一级 | 园豆:3
提问于:2019-09-01 17:40

建议提供一下 MySqlHelper.ExecuteScalarAsync 部分的代码

dudu 5年前

@dudu: public static Task<T> ExecuteScalarAsync<T>(string sql, object param = null, string connStr = null, int? commandTimeout = null)
{
using (var conn = GetDbConnection(connStr))
{
return conn.ExecuteScalarAsync<T>(sql, param, commandTimeout: commandTimeout, commandType: CommandType.Text);
}
}

爱杨洋真是太好了 5年前

@dudu: 其实就是调用了dapper类库提供的ExecuteScalarAsync方法

爱杨洋真是太好了 5年前
< >
分享
最佳答案
0

MySqlHelper.ExecuteScalarAsync 方法中要加上 async/await

收获园豆:10
dudu | 高人七级 |园豆:29568 | 2019-09-01 18:32

试过了,加和不加其实效果一样

 

public static async Task<T> ExecuteScalarAsync<T>(string sql, object param = null, string connStr = null, int? commandTimeout = null)
{
using (var conn = GetDbConnection(connStr))
{
return await conn.ExecuteScalarAsync<T>(sql, param, commandTimeout: commandTimeout, commandType: CommandType.Text);
}
}

猴哥aiyy | 园豆:3 (初学一级) | 2019-09-01 18:34

@爱杨洋真是太好了: 再试试

dudu | 园豆:29568 (高人七级) | 2019-09-01 18:37

@dudu: 试了100次,全部都是一样的,正常来讲await后面的代码相当于是回调,基本是不可能与主线程一样的

猴哥aiyy | 园豆:3 (初学一级) | 2019-09-01 18:39

@dudu: .net core 2.2下的控制台调试的

猴哥aiyy | 园豆:3 (初学一级) | 2019-09-01 18:40

@爱杨洋真是太好了: 难怪,控制台程序就一个主线程,换成 web 程序

dudu | 园豆:29568 (高人七级) | 2019-09-01 18:44

@dudu: 使用web

public async Task<IActionResult> Index()
        {
            Person person = new Person()
            {
                Name = "侯哥",
                Age = 25,
                Sex = 1
            };
            var logger= _loggerFactory.CreateLogger<HomeController>();
            logger.LogInformation(System.Threading.Thread.CurrentThread.ManagedThreadId.ToString("00"));
            string sql = @"INSERT INTO `Person` (`Name`, `Sex`, `Age`) VALUES (@Name, @Sex, @Age); ";
            int a= await MySqlHelper.ExecuteAsync(sql, person);
            logger.LogInformation(System.Threading.Thread.CurrentThread.ManagedThreadId.ToString("00"));
            logger.LogInformation("************************************");
            return View();
        }

试了很多次,结果是这样

猴哥aiyy | 园豆:3 (初学一级) | 2019-09-01 19:08

@dudu: 还有个问题,就是如果2个线程不一样的话,我理解的是request是基于当前线程的,那么await后面的代码是不是就拿不到request信息了呢?

猴哥aiyy | 园豆:3 (初学一级) | 2019-09-01 21:11

@爱杨洋真是太好了: await 是释放当前线程,异步执行完成后从线程池中取一个线程继续执行,如果没有什么并发,线程池中的线程很少,很可能从线程次拿到的也是之前释放的线程

dudu | 园豆:29568 (高人七级) | 2019-09-01 22:02

@dudu: 我也是这样理解的,await后面的代码相当于是一个回调,内部是一个状态机的实现方式。

现在我不使用dapper提供的异步方法,我自定义一个异步的方法,如下

public async Task<IActionResult> Index()
        {
            object o1 = Request;
            var logger= _loggerFactory.CreateLogger<HomeController>();
            logger.LogInformation(System.Threading.Thread.CurrentThread.ManagedThreadId.ToString("00"));
            int a =await AsyncTest();//自定义的异步方法
            logger.LogInformation(System.Threading.Thread.CurrentThread.ManagedThreadId.ToString("00"));
            object o2 = Request;
            Console.WriteLine(object.ReferenceEquals(o1,o2));//打印出来的true
            return View();
        }

        public static async Task<int> AsyncTest()
        {
            return await Task.Run(() => {
                return 1;
            });
        }

现在前后的线程ID是不样的,这个可以说的通,因为是await后面的代码是从线程池中重新取的一个线程,那么现在2个request竟然是同一个,这个是框架本身同步的吗?

但是我使用dapper框架的异步方法就会导致前后线程是一样的,这个就很难讲的通,dapper的方法返回的也是一个Task<int>,难道dapper内部的实现不是通过task实现的吗? 

猴哥aiyy | 园豆:3 (初学一级) | 2019-09-01 22:20

@爱杨洋真是太好了: 能否提供一下重现这个问题的示例代码,我明天找时间测试一下

dudu | 园豆:29568 (高人七级) | 2019-09-01 22:27

@dudu: 可以,代码怎么发你

猴哥aiyy | 园豆:3 (初学一级) | 2019-09-01 22:39

@爱杨洋真是太好了: 上传到 github

dudu | 园豆:29568 (高人七级) | 2019-09-02 07:40

@dudu: 地址:https://github.com/houwencheng1993/KTSCRMApi

直接运行,就可以,代码在KTSCRM.Admin   home index下面,使用dapper时需要访问数据库

猴哥aiyy | 园豆:3 (初学一级) | 2019-09-02 10:30

@爱杨洋真是太好了: 是线程池线程太少的原因,可以在 Startup.Configure 中添加下面的代码增加线程数

if (ThreadPool.SetMinThreads(10, 10))
{
    Parallel.For(0, 10, a => Thread.Sleep(1000));
}
dudu | 园豆:29568 (高人七级) | 2019-09-04 15:51

@dudu: 我是.net core 2.2的版本,设置 SetMinThreads(50, 50),await前后线程打印出来还是一样,我怀疑与dapper的异步方法的实现有关系

猴哥aiyy | 园豆:3 (初学一级) | 2019-09-04 21:14
其他回答(2)
0

前后是否同一线程取决于Async方法内部。
通常来说,调用真正的异步,方法的前后线程是不一样的。
但是,有些情况下,异步是假的。比如
public static async Task<int> AsyncTest(int x)
{
if (x == 0) return 0; // 假异步,同步执行
if (x == 1) return await Task.FromResult<int>(0);//假异步,同步执行
return await Task.Run(() => x);//真异步
}

pencile | 园豆:845 (小虾三级) | 2019-09-02 19:58

那真异步下await前后如何

保证request相同呢

支持(0) 反对(0) 猴哥aiyy | 园豆:3 (初学一级) | 2019-09-02 20:27

@爱杨洋真是太好了:
使用阻塞的方式,如:
Task<int> t= AsyncTest(123);
//do something
var result=t.Result; //或 t.Wait();

支持(0) 反对(0) pencile | 园豆:845 (小虾三级) | 2019-09-02 21:55

假异步下确实线程是相同的,关于request的问题是说现在await前后线程确实不一样了,但是打印出来的request是相同的,感觉这个是框架本身做了什么

支持(0) 反对(0) 猴哥aiyy | 园豆:3 (初学一级) | 2019-09-04 21:34
0

这个应该是利用Oracle驱动程序(ODP.NET)的方法不是真正的异步方法,里面是阻塞的,所以每次前后的线程ID都是一样的

kabukabu | 园豆:202 (菜鸟二级) | 2020-09-18 09:48
清除回答草稿
   您需要登录以后才能回答,未注册用户请先注册