C# 异步方法,关于Task,async/await 使用的疑惑?求教谢谢

Haitani 2020-08-28 09:50:28
在学习任务Task和异步编程相关知识的时候,有些疑惑,比如我执行一个耗时方法DoForLoop(),直接利用Task.Run()就可以实现异步了。而新机制async/await,需要写一个异步方法DoForLoopAsync(),异步方法DoForLoopAsync中还是需要Task.Run()去实现具体的功能并返回Task<T>,这样不是多此一举么?不如Task.Run()直接执行就好了?没理解透什么意思....
求教各位前辈,async/await的正确用法,还是说async/await仅仅是一种规范,一种约定?谢谢了,另外贴一段测试代码,请教这三种方式区别,谢谢了

//方式1:直接Task.Run
private void btnTest_Click(object sender, EventArgs e)
{
Task.Run(new Func<string>(DoForLoop));
}

//方式2:
private void btnTest2_Click(object sender, EventArgs e)
{
DoForLoopAsync();
}

//方式3:加async,await执行
private async void btnTest3_Click(object sender, EventArgs e)
{
await DoForLoopAsync();
}

//普通方法
private string DoForLoop()
{
for (int i = 0; i < 10000; i++)
{
Console.WriteLine(i);
}
return "完成";
}

//异步方法
private Task<string> DoForLoopAsync()
{
return Task.Run(() =>
{
for (int i = 0; i < 10000; i++)
{
Console.WriteLine(i);
}
return "完成";
});
}
...全文
1569 15 打赏 收藏 转发到动态 举报
写回复
用AI写文章
15 条回复
切换为时间正序
请发表友善的回复…
发表回复
  • 打赏
  • 举报
回复
嗯,上面的 btnTest3_Click 方法前边的 async 关键字也应该去掉。

实际上在你的脑子中,正如这里的代码 btnTest3_Click 并不需要声明 async,以及代码 DoForLoopAsync 代码并不需要 await 来等待它返回一样,你其实没有在 async/await 的主要道路上去理解它,而只是从一些边边角角的概念上去理解它。在你的实际概念中,正如这里修改完之后的代码一样,Task.Run 方法所创建的 Task 对象实际上被你扔掉了,你根本没用到 Task 的主要机制。你只是用到了它“可以在创建任务并从线程池分配线程”这每一种底层特性。

一个 Task 它有什么用?假设你设置一个“外部Task对象”,例如
private Task t;

async Task<int> test()
{
var a= 1;
.......
t= new Task(()=>Console.WriteLine("hello")); //这里创建一个任务,任务只是随便打印一句话而已
await t;
......
return a+1;
}

void proc()
{
.......
t.Start();
.......
}
这里,test 执行到中间时就跳出去了。直到另外有某个方法调用 t.Start,就会让 test 跳出的那个语句下边的代码继续被执行。这里看到了 await 功能,看到了环境变量(例如 a)管理功能,看到了一个 async/await 可以返回数据结果给上层的同样结构的 async/await 异步控制结构的功能。
  • 打赏
  • 举报
回复
关于你写的代码,我没有细看,就事论事来看的话你这里有多余的东西:
        private async void btnTest3_Click(object sender, EventArgs e)
{
DoForLoopAsync();
}

//普通方法
private string DoForLoop()
{
for (int i = 0; i < 10000; i++)
{
Console.WriteLine(i);
}
return "完成";
}

//异步方法,此方法不等循环结束,立刻完成
private async void DoForLoopAsync()
{
return Task.Run(() =>
{
for (int i = 0; i < 10000; i++)
{
Console.WriteLine(i);
}
return "完成";
});
}


也就是说你其实多于写了
await 
关键字。既然是多于写了这个关键字,就说明你还没有明白 await 真正的意义。
  • 打赏
  • 举报
回复
在传统的异步代码中,我们需要设计“回调”函数,以及函数所跳入和跳出的环境对象,需要专门设计的代码。例如你使用
TcpClient.GetStream().BeginRead(......callback.....)
来注册一个回调委托,那么委托方法里边要调用 EndRead方法,而两个不同代码场景之间的环境变量需要协同,这需要设计一些中间数据结构。

特别是这类回调异步有可能有多个异步连缀。例如这里的 callback 实现中就需要再次调用 BeginRead 来让 client 能够继续监听新数据。我们经常需要先一步调用 A,然后在 A 过程中异步调用 B,然后在 B 过程中异步调用 C ....... 这时候写代码是很繁琐冗长的。只有实践者才明白。

async/await 语法可以让你使用与过去的同步顺序代码几乎一模一样的语法,代替累赘的旧语法。代码清爽了很多,开发速度提高了10倍。但是前提是,你真的在脑子里有需要“非阻塞 await”的相关调试经验。
正怒月神 2020-08-28
  • 打赏
  • 举报
回复
区别是 你是否 await了啊。。。。
palhotel 2020-08-28
  • 打赏
  • 举报
回复
用我自己的话解释:函数前声明async,说明这个函数可能有异步执行的代码块。当这个函数内部有了await 语句时,“等待”了await 这一行的调用获取了结果,更重要的是await这行下边的语句相当于在异步调用的回调函数里执行,只不过是以顺序的方式写在那里了。如果这个函数内部没有await的语句那么它就跟普通的函数执行一样。await一定要在声明了async的函数内使用。 如果你只执行一个异步任务,也不在意他的结果,例如下订单之后发送一条短信,也没有后续动作,无所谓,Task.Run或者是不写await调用一下,都是可以的。 如果你要做一个获取access token之后立即请求用户信息这样的顺序操作,而接口都是异步的,过去都会用回调函数,让你的两次调用不在一个地方,可能影响可读性,用这个async/await就能让它们看起来像顺序执行一样。
wanghui0380 2020-08-28
  • 打赏
  • 举报
回复
很多人说是语法糖,那是因为逻辑上就是这么个逻辑,现在写个 await 就好。而以前完成这个的时候,那叫一个千奇百怪各出奇招 高能力的搞线程同步,线程信号量 中能力的搞回调 低能力的折腾4中timer区别,弄个定时器去刷,看看结果出来没。 so,现在语法糖,逻辑还是那个逻辑,只是微软说别纠结了,既然逻辑就是异步等待,我就给你个异步+等待两个专用token,简单直白的做,别各出奇谋了
wanghui0380 2020-08-28
  • 打赏
  • 举报
回复
一线程序员从来不跟名词纠结,俺们只跟逻辑纠结。什么同步异步,这个vs那个,都不重要 我们只知道,我现在要做的逻辑是“我需要执行一个耗时比较长的功能,我希望他执行玩了我能拿到执行的结果,同时我希望程序不堵在那里,在他执行这个比较长的功能的同时,我能干点别的事情(比如你累加10000的同时,我的鼠标还能动,窗口还能移动)”
wanghui0380 2020-08-28
  • 打赏
  • 举报
回复
太过纠结不是好事 没有什么,用什么封,不用什么封,多此一举。已经回复烂的一句话“程序员根据逻辑写代码,不是代码让你程序员这么写,是逻辑让你这么写” Task.Run() 本身就可以await 至于你要不要await,是你的逻辑告诉你,我要不要等待结果。不要等待结果就不await,想等待结果就await 比如测试过程,所有的调用你后跟一个 messagebox.show("完成") 你在去看效果。 没有await的,立刻弹框。有await是等到你运行完毕了在弹框
八爻老骥 2020-08-28
  • 打赏
  • 举报
回复
多核处理器的机器上,这种方式是为了充分利用CPU的性能,即使是同步的代码,也可以由不同的核来运行,而你只要加一个await就可以实现了。
wanghui0380 2020-08-28
  • 打赏
  • 举报
回复
太多人喜欢跟着博客园那样纠结名词了。不await就同步,异步了
引用
这词不重要,重要的是“主进程还有能力做其他的事情” 对,喜欢纠结名词的人民,看到上面的描述。肯定会有另外一个名词“多线程”
引用
但是问题来了,多线程了,我让知道结果怎么办 对,喜欢纠结名词的人民,看到上面的描述。又会说另外一个。“回调”
引用
问题又来了,回调委托,+= 到另外一个方法里了,参数丢失了。e 对,喜欢纠结名词的人民,看到上面的描述,又会说另一个词,上下文参数。(context,state)
引用
问题又来了,有多线程了,有回调了,有上下文了,我的程序似乎变得支离破碎了。东一个线程,西一个回调,我想保持一个顺序得逻辑。 比如 线程 线程执行, where(线程执行完毕) { 继续做下面得事情。//这代码是不是比上面说得那一套东西要看着顺畅点 } 但是他是同步代码啊。 对,喜欢纠结名词的人民-------------又出新名词了,“timer”--------你用定时器啊
引用
然后嘛,又来问题了。用timer 代码段也不在一快啊,依旧东一块西一块,而且还有所谓的4种timer啥区别,联系,这个timer vs 那个timer。而且还要控制启停 对,喜欢纠结名词的人民-------------又出新名词了,信号量。做之前加格锁,线程做完,释放锁 加锁信号量 线程 线程1={ 释放信号量,上下文参数} 等待信号量 ok,我完成了任务了,没有堵主线程,多线程完成了,而且能执行完的结果(上下文参数),而且这些代码还就在一块,没有东一块,西一块,看着方便
最后,微软说,统统给我闭嘴,写代码就是写逻辑,不是写名词,不要各种名词来名词去。我就给你两个词“await/async”都给我消停点,别闹了
正怒月神 2020-08-28
  • 打赏
  • 举报
回复
使用正确的测试用例,才能推导出你要的结果。 楼主只是跑了一个不需要返回的task。 这个不能证明是否阻塞的问题。也不是异步的体现。 就拿老生常谈的做饭例子来说。 你一遍切肉,一遍用微波炉热饭。 异步是,你切肉时,微波炉中的饭热好了,然后“叮”一下,他告诉你可以去拿了。 而,楼主的例子,只是你切肉时,微波炉中的饭热好了,也没有"叮"一下,楼主也没想过去拿饭。
正怒月神 2020-08-28
  • 打赏
  • 举报
回复
引用 6 楼 ∽油条á 的回复:
[quote=引用 5 楼 正怒月神 的回复:]区别是 你是否 await了啊。。。。
你的意思就是有await的就是异步执行了?[/quote] 针对你这个问题,其实就是这样。 我把楼主的代码,完善一下,增加了thread.sleep 然后,大家可以操作看看。 首先,界面是这样的(红框内) 然后我修改了代码,也就是在DoForLoopAsync()方法,增加了thread.sleep(2000);
public Form1()
        {
            InitializeComponent();
        }
        
        private void Form1_Load(object sender, EventArgs e)
        {
            btnTest.Click += btnTest_Click;
            btnTest2.Click += btnTest2_Click;
            btnTest3.Click += btnTest3_Click;
        }

        //方式1:直接Task.Run
        private void btnTest_Click(object sender, EventArgs e)
        {
            var q = Task.Run(new Func<string>(DoForLoop));
            test1.Text = q.Result;
        }

        //方式2:
        private void btnTest2_Click(object sender, EventArgs e)
        {
            var q = DoForLoopAsync();
            test2.Text = q.Result;
        }

        //方式3:加async,await执行
        private async void btnTest3_Click(object sender, EventArgs e)
        {
            var q = await DoForLoopAsync();
            test3.Text = q;
        }

        //普通方法
        private string DoForLoop()
        {
            for (int i = 0; i < 10000; i++)
            {
                Console.WriteLine(i);
                
            }
            Thread.Sleep(2000);
            return "完成";
        }

        //异步方法
        private Task<string> DoForLoopAsync()
        {
            return Task.Run(() =>
            {
                for (int i = 0; i < 10000; i++)
                {
                    Console.WriteLine(i);
                }
                Thread.Sleep(2000);
                return "完成";
            });
        }

    }
最后,当你点击,btnTest1和btnTest2时,整个winform是阻塞的,卡住了。 而btnTest3,是不会阻塞的,他在winform界面,可以继续点击其他的textbox并获得光标。 当然,如果你只是让一个线程去跑,不用返回结果。那完全可以忽视上面的内容。 但是按照楼主的测试用例,其实根本不能支持他的论点。 建议楼主换成我这个,然后在跑一下看看。 顺带,给大家看一下 Result的解释,说白了他是同步的。
xuzuning 2020-08-28
  • 打赏
  • 举报
回复
async/await : 用同步的形式写异步操作
wanghui0380 2020-08-28
  • 打赏
  • 举报
回复
引楼上: 你的意思就是有await的就是异步执行了? 不纠结,和博客园的粉不同。我们不纠结错误,我只说1000种,1w种错误。知道他是错的就好,没必要动则就上纲上线。掌握正确的比纠结错误的要有效 电视上动则,油不好,盐不好,酱油不好,味精不好,鸡精不好,辣椒不好,花椒不好。请问你吃撒把?白水煮么? 所以不是纠结好不好,是养成正确的习惯,你平时做的就是正常的,健康的就行。 而且你真以为那些天天纠结错误的,吃的就健康?错,我认识一个人,我做菜从来不让他站边上滴,因为他就是和博客园粉那样,看点东西都了不得的,他站你边上你是不能放盐,放酱油,放味精滴?so,你觉着他真如此健康,错,他不放味精,他放老干妈。他不放酱油,他放豆豉滴。(是滴,电视上没告诉他老干妈,豆豉比酱油,味精还可怕呢)
∽油条á 2020-08-28
  • 打赏
  • 举报
回复
引用 5 楼 正怒月神 的回复:
区别是 你是否 await了啊。。。。
你的意思就是有await的就是异步执行了?

110,533

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Web++
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧