为什么要干掉那些喜欢卖弄细枝末节的家伙.
比如有一句话:
await DoAsync();
这样做挺好的,也好理解.
然后那个叛徒偏偏要在某处丢下这么一句:
_ = DoAsync();
喵了个咪,老夫一不小心就被带到沟里,还查半天这个怎么运作的,怕有坑.
就算了解了,对项目有毛线关系.
直接 await !
不好好做项目整天把时间纠结在这种问题上有意义吗. 这种卖弄小技术的人真是可恶啊.
比如有一句话:
await DoAsync();
这样做挺好的,也好理解.
然后那个叛徒偏偏要在某处丢下这么一句:
_ = DoAsync();
喵了个咪,老夫一不小心就被带到沟里,还查半天这个怎么运作的,怕有坑.
就算了解了,对项目有毛线关系.
直接 await !
不好好做项目整天把时间纠结在这种问题上有意义吗. 这种卖弄小技术的人真是可恶啊.
比如发个 log 日志到服务器,方法是 task SendLogAsync(),因为有网络操作,执行时间是 1 秒
那么下面代码:
DoA()
SendLogAsync()
DoB()
这段不用 await,表示 DoB()不需要等待 SendLogAsync()做完,也就是 DoA 后不需要等待 1 秒,而是直接 DoB
而下面代码:
DoA()
await SendLogAsync()
DoB()
这段加了 await,表示 DoB()需要等待 SendLogAsync()做完,也就是要 DoA 后等待大约一秒后才 DoB
你举的这个例子,首先就应该把 SendLogAsync()这个函数的 Async 去掉,变成 SendLog(),然后再用
DoA()
SendLog() => 在这里面怎么搞随便你,总之快点返回就行了.
DoB()
实际项目中一会要 await, 一会儿又要迅速返回,那多半是设计逻辑就有问题.
劝你别杠了.


PingWorker 这里不是正好正好封装了 PingAsync 吗.
另外我好好想了一下,你说的立即返回本身也不对.
如果 DoAsync 里面有一个 await Do2Async(),事实上就卡住 Do2Async 这里异步了.
对于普通开发人员来讲,不了解内部代码情况下,直接调用 DoAsync 就是不合规范的,所以必须先封装.
首先不一定能达到想要的目的(立即返回),其次这样迫使检查人员还需要检查内部代码,增加维护难度.
一句话,符合规范才是最重要的. 对于极端情况,就封装使其符合规范.
DoA()
SendLogAsync()
DoB()
如果 SendLogAsync()内是
Task SendLogAsync()
{
…
await DoSomethingAsync();
…
}
DoB()并不会因为 SendLogAsync()调用而立即执行
关于执行的流程我确实没有清楚. 但是这个不是问题的关键所在,因为按照规范我也不会这么去写,也就不会跟你纠结 DoB 是否会立即执行这种问题,很简单,对我来说异步方法就是异步调用,如果要把异步方法转为同步调用必须谨慎封装. 我也没看出我的封装有任何问题和难以理解的地方.
而从另一方面讲,你那种调用方法有非常不安全的地方. 你无法保证能捕获到 SendLogAsync()内的异常,甚至无法保证它能够完整执行. 当然这个问题在我的封装内也存在,所以谨慎封装是必须的.
所以综合考虑,我并不认为向你所说的直接调用一个异步方法是正确的. 当够避免的情况下更应该避免. 在我司的项目中更完全没有必要直接调用,这就是炫技狂真正的问题所在.
异步包括 task,也包括 IAsyncOperation 方法。我说的意思就是说返回 IAsyncOperation 的更不能直接调用.
IAsyncOperation 方法是否会阻塞 DoB 我还没有验证,但是你说的 3 这点就错了.
2, task 就是 IAsyncAction , task<T> 就是 IAsyncOperation 。只是叫法不同,习惯叫前者。
另外还有 c#7.0 开始新加的 GetAwaiter (也叫 ICriticalNotify? 手机打字,准确名称有点记不得了)和 IAsyncEnumerable<T> ,后面两个你可以不管,弄懂 task 和 task<T> 就能触类旁通了
3,你别跟我争了,经历过 APM ( IAsyncResult )年代的人,平滑切入 TAP (基于任务的异步,就是你所谓的 async 和 await ),会自然明白设计原理。你没写过代码,直接写 TAP,就会知其然不知其所以然
async Task TestAsync()
{
/// do something
}
编译完后等价于
class TestAsync1 : IAsyncStateMachine
{
public AsyncTaskMethodBuilder t_builder;
public void MoveNext()
{
try
{
/// do something
}
catch (Exception exception)
{
t_builder.SetException(exception);
return;
}
t_builder.SetResult();
}
public void SetStateMachine(IAsyncStateMachine stateMachine)
{
this.SetStateMachine(stateMachine);
}
}
Task TestAsync()
{
TestAsync1 ta = new TestAsync1();
ta.t_builder = AsyncTaskMethodBuilder.Create();
ta.t_builder.Start(ref ta);
return ta.t_builder.Task;
}
所以直接调用 TestAsync() ,并不会所谓卡住。
你可以自己想一想加 await 之后的状态机,这个也是学习的必经之路。
另外我根本不需要跟你纠结内部机制,内部机制下还有内部机制,你觉得你能全部搞清楚?不遵守规范就是挖坑,只能越挖越多. 正如我刚才所说,即使我不清楚内部细节,我按照规范写也不会出问题. 但是一个自以为懂的人,却很容易陷入自己挖的坑里无法自拔.
你现在论证的模式,就是越挖越深. 扯出来的技术越来越多,本质上就是炫技,还增加错误的风险.
如果不用 await
var mem = new Byte[1024*1024*1024]; //申请 1g 内存
DoAsync();
在下次 GC 时,mem 就会被释放
2,异常捕获,通常来说会写一个 extention method 来做
public static class Tool
{
public static void IgnoreException(this Task t)
{
t.ContinueWith(c => { var ignore = c.Exception; });
}
}
DoA()
SendLogAsync().IgnoreException();
DoB()
这样就可以忽略异常,不要问为什么
我终于看明白你的意思了……
public static async Task SendLogAsync()
{
// 这一行属于 在没有遇到一个实质性的 task 前
Console.WriteLine(“SendLogAsync {0}”, DateTime.Now.ToString(“ss.fff”));
// await Task.Delay(300);
}
SendLogAsync()
DoA()
所以你以为不加 await, 就是相当于同步执行,先执行 SendLogAsync 里的没有遇到一个实质性的 task 前的方法, 再执行 DoA()
await SendLogAsync()
DoA()
所以你以为加了 await,就是异步执行,先执行 DoA(),再执行 SendLogAsync 里的没有遇到一个实质性的 task 前的方法
============以上是你的错误理解===============
不是的,看下面的例子
https://dotnetfiddle.net/9jS6s4
无论加不加 await,都是相当于直接在 26 的代码执行第一个 movenext
还有异常捕获,我的意思是 SendLogAsync()这样调用本身就是高风险的, 我提出这个问题后你才给出解决方案. 如果我不提出这个问题这里面就相当于挖了一个坑. 而且还没有保证 SendLogAsync()能够顺利执行完毕. 还得继续挖. 这就是炫技带来的一系列后果. 遵循业界常规这问题根本就不会出现.
另外再举个例子吧. 如果 SendLogAsync()内部是这样实现的
public static async Task SendLogAsync()
{
Int64 num = 100000000;
Int64 i, j=0;
for (i = 2; i < num; i++)
{
if (num % i == 0)
{
j = i;
}
}
await Task.Delay(1000);
}
这样
DoA()
SendLogAsync()
DoB()
根本达不到想要的效果
是不是 SendLogAsync()内部不碰到 await 就不立刻退出了. 是不是必须检查内部代码实现>
相反用封装
public static void SendLog()
{
Task.Run(async ()=>{ await SendLogAsync();});
}
再调用
DoA()
SendLog()
DoB()
则含义清晰,而且到 SendLog()后就立刻返回,完全达到想要的效果而且不违反规范,还无需知晓内部的实现
遵守规范编程,概念清晰,无需挖坑,无需纠结内部细节.
不遵守规范编程,概念混淆,常常容易挖坑,还要纠结内部实现.
孰优孰劣,一测便知.
async/await 不是什么需要炫技的东西,找篇文章看一看就能懂,花不了多少时间。
语法糖不是规范,而是让 os 底层方法方便使用而已。毕竟有一大堆连基本线程调度机制都搞不懂的人。常看微软文档的人应该经常能看见这样的表述:”不建议你使用 xxxx 写法,除非你清楚知道自己在做什么”,理解”除非”前后的差距,正确认识自己的不足。
人家没有说你”菜”, 没有说你”水”,谁都是过来人。而你却满口”杠”,”炫技”, “狂人”。自己想一想把。机灵点的小伙子,说几句漂亮话,聊聊共同爱好,私下加个微信,可能很快就从”对 C#不熟”过度到”熟练”了。技术这条路,戒骄戒躁。言尽于此,爱咋咋。
@nannanziyu
我真的没兴趣纠结 await 内部是怎么实现的,什么 movenext 不 movenext 真不关心. 我提出的这些其实都是根据你说的需要“立即返回”来说的,我指出这样并不一定会立即返回. 如果用 await 来说,我本来就知道 DoB()需要等待,那么 await 后面的函数内部代码没碰到 await 前和碰到 await 后是个什么情况并不重要啊!!
我已经累了,我想我说得够清楚了.
至于你最后一段话,我就不发表评论了.
搞技术的人走火入魔标志:
我偏要这么搞,你看不懂是你的问题,不是我的问题.
但是 async 的不一定非要 await 吧
你要比出活儿快,那你自己攒一套最佳实践是出活儿快
你要比容错率,你要看很多很多代码才可以,而且 心平气和地看也不被搅乱,因为头脑里有更深的东西,在那个标尺之下 是有东西
你真正有容错率之后才能指导别人,半碗水晃荡一下子就露馅了阿。在那个标尺之下,你没东西。所以在这里,你没有容错率
今天没有人跟你杠,总有一天有人跟你杠的阿 ( 只要你踏入那个层次 ),如果就这么轻易地被杠翻了,那么 … 要么 如果你不肯开拓 (为自己开拓新标尺 新层次,给自己出出题) 的话 你就得承认你混错圈子了:把踏入的脚 要收回来
注意:求仁得仁,我没说哪个高 哪个低,哪个标尺好 哪个标尺不好。你求仁得仁。但确实是不同的标尺。
是啊,所以的 Async 方法都不能保证,所以 await 还有啥意义?
所有的 await 方法都要考虑会不会有菜鸡在写耗时同步执行,那所有的方法都用 Task.run 开个线程池执行吧
你这不是傻的嘛
我就问一句,在 await DoAsync()没有任何问题的情况下,你会不会用 DoAsync()?这样乱用是不是脑子进水了,你直接回答这个问题就结了.
如果 DoAsync() 结果无意义,那么当然不用 await 啊
因为用了 await,后续操作会无意义的延时啊
你真的连这个都不明白吗?
但是推荐直接调……
1,如果返回结果和执行状态无意义,那么直接调就可以了
await DoA();
SendLogAsync(“A”);
SendLogAsync(“B”);
SendLogAsync(“C”);
SendLogAsync(“D”);
SendLogAsync(“E”);
SendLogAsync(“F”);
SendLogAsync(“G”);
await DoB();
因为假如都加 await,真正需要的 DoB 会被无意义的延迟很多秒
2,假如这些 SendLogAsync 本身是有序的,也就是执行完第一个,才能执行第二个,以此类推。但是整个 SendLog 的返回结果和执行状态无意义,那么才需要用 Task.Run 来包
await DoA();
Task.Run(async () =
{
await SendLogAsync(“A”);
await SendLogAsync(“B”);
await SendLogAsync(“C”);
await SendLogAsync(“D”);
await SendLogAsync(“E”);
await SendLogAsync(“F”);
await SendLogAsync(“G”);
}
await DoB();
哪怕是所有都认为主错了,主仍然还觉得自己观点正确,对自己丝毫没有怀疑,睥睨天下。纯粹。
哪怕是被冠上了”炫技狂”,”走火入魔”,”脑子进水”这类明着的侮辱词汇,@nannanziyu 兄直到几十层最后两句还在试图纠正主,这是另一份纯粹,值得敬佩。这位兄弟的昵称也值得品味,nannanziyu, 喃喃自语。
再说其他旁观者,什么鲁迅体嘲讽,什么求仁得仁,什么天桥下面的卖丑,word 天,整出一堆文学家,你们说话怎么那么好听,真得发出去让别人看看,这是行外人对程序员的刻板印象的一次强硬反击。
我要特别说说这位兄弟 @weiceshi,你天生带着一股幽默,”主,你是怎么达成“一整篇帖子,自己几十个回复”,一个正确的观点都没有的”, 看到你这句话我就已经快忍不住了,又看到你后面的包十层包大人我的小口口里面的水水直接就喷出来,把我的衣物都高湿了。
哪怕是被冠上了”炫技狂”,”走火入魔”,”脑子进水”这类明着的侮辱词汇,@nannanziyu 兄直到几十层最后两句还在试图纠正主,这是另一份纯粹,值得敬佩。这位兄弟的昵称也值得品味,nannanziyu, 喃喃自语。
再说其他旁观者,什么鲁迅体嘲讽,什么求仁得仁,什么天桥下面的卖丑,word 天,整出一堆文学家,你们说话怎么那么好听,真得发出去让别人看看,这是行外人对程序员的刻板印象的一次强硬反击。
我要特别说说这位兄弟 @weiceshi,你天生带着一股幽默,”主,你是怎么达成“一整篇帖子,自己几十个回复”,一个正确的观点都没有的”, 看到你这句话我就已经快忍不住了,又看到你后面的包十层包大人我的小口口里面的水水直接就喷出来,把我的衣物都高湿了。一句天桥下面卖丑,眼瞅着你就要暴躁起来了,可能是被 @nannanziyu 的行为所征服,突然画风一转,完成了个人思想道德的升华。有意思,有意思。
我举出一个更好的例子可以说明为什么要按照规范来写.
因为你无法知晓 SendLogAsync 本身是不是线程安全的(尽管在这个场景下大概率是,但是如果是别的场景呢,这点我想不用解释了吧),所以在没有审查源码的情况下,使用你的方法 2 才是 ok 的,跟是否需要得到返回结果没有关系.
这个坑还是我目前想到的,说不定还有更多坑.
这个用了 await 了,然后 window 大小连续更改了两次,调用了两次 Func(),你一样要考虑线程同步的问题,你不明白这个吗?不能用 await 来解决线程同步的问题啊
解决线程同步对你来说太深了,你可以了解下 SemaphoreSlim……
哥呀,你真的……
不过没事,有进步就好,毕竟有先贤说过“想要获得一个对的答案,那么最好的方法就是先给出一个错误的来”
主通过先贤的方法,while(true) {await 卖丑; await 获取知识; await google 下一个华点;} 的过程值得我们借鉴;不过要是天桥献艺的同时,能给个打赏二维码就好了,知识铜币两不误嘛,对不对
这是我设计的 SendLogAsync() ,我还没设计成线程不安全,只是为了说明可以创建线程在里面
private async Task<Int64> SendLogAsync()
{
Int64 j = 0;
await Task.Run(() =>
{
Int64 num = 600000000;
Int64 i = 0;
for (i = 2; i < num; i++)
{
if (num % i == 0)
{
j = i;
}
}
});
Debug.WriteLine(“{0} SendLogAsync”, j);
return j;
}
另外,
public void DoA()
{
Debug.WriteLine(“{0} DoA”, DateTime.Now.ToString(“ss.fff”));
}
public void DoB()
{
Debug.WriteLine(“{0} DoB”, DateTime.Now.ToString(“ss.fff”));
}
调用
for (var k = 0;k<3;k++)
{
DoA();
await SendLogAsync();
DoB();
}
结果
18.225:{0} DoA
300000000 SendLogAsync
22.357:{0} DoB
22.358:{0} DoA
300000000 SendLogAsync
26.468:{0} DoB
26.469:{0} DoA
300000000 SendLogAsync
30.612:{0} DoB
改成
for (var k = 0;k<3;k++)
{
DoA();
SendLogAsync();
DoB();
}
结果
08.930:{0} DoA
08.942:{0} DoB
08.943:{0} DoA
08.986:{0} DoB
08.988:{0} DoA
09.014:{0} DoB
300000000 SendLogAsync
300000000 SendLogAsync
300000000 SendLogAsync
这说明的问题应该很清楚了吧. 你举的例子在这里不会发生线程冲突好不.
但是如果直接下面一种调用,如果有共享变量明显会有线程冲突的可能.