为何无法在async方法前使用await关键字?C#异步文件操作相关疑问
嘿,我来帮你把这些异步操作的疑问掰扯清楚~结合你写的代码,咱们一个个说:
问题1:调用WriteAsync(Byte[], Int32, Int32)前必须使用await运算符吗?
语法上不是必须的,但从逻辑和正确性来说,几乎所有场景下都必须用await!
你看你的代码片段1,循环里直接调用fileStream.WriteAsync(data, 0, data.Length),然后马上在循环结束后调用fileStream.Close()——这里有个大问题:WriteAsync是异步执行的,它会立刻返回一个Task,但实际的写入操作可能还在后台跑着呢!你这时候直接关闭流,很可能导致写入操作失败(比如抛出IOException),或者写入的数据不完整,因为流已经被提前释放了。
加上await的作用就是告诉程序:“等这个异步写入操作彻底完成了,再继续执行后面的代码”。修正后的代码应该是这样:
while (fileLength < 100) { i = i + 1; sampleText = string.Format("Line {0} of {1} \n", i, lineCount); data = encoding.GetBytes(sampleText); await fileStream.WriteAsync(data, 0, data.Length); // 加上await fileLength = fileLength + data.Length; Console.WriteLine("Length of file written: " + fileLength); } fileStream.Close();
而且更规范的写法是用using语句自动管理流的生命周期,避免手动Close的问题:
using (var fileStream = new FileStream("yourfile.txt", FileMode.Create)) { while (fileLength < 100) { i = i + 1; sampleText = string.Format("Line {0} of {1} \n", i, lineCount); data = encoding.GetBytes(sampleText); await fileStream.WriteAsync(data, 0, data.Length); fileLength = fileLength + data.Length; Console.WriteLine("Length of file written: " + fileLength); } } // 这里会自动关闭流,且保证所有异步操作完成后才执行
问题2:是否可以不使用await运算符直接调用ReadAsync(Byte[], Int32, Int32)?
语法上允许,但逻辑上几乎都是错误的,除非你完全不在乎读取的结果,也不担心操作失败(这几乎不可能)。
你代码片段2里的while (fileStream1.Position == fileLength) { fileStream1.ReadAsync(data, 0, 100); },这里的问题在于:ReadAsync还没来得及把数据读到data里,循环就又开始判断条件了,相当于你一直在发起读取请求,但根本没等请求完成,也没处理读取到的数据。而且未被等待的Task如果抛出异常,会变成未观察到的异常,在老版本的.NET里甚至会导致程序崩溃。
如果你的目的是等待读取到指定位置,正确的写法应该是:
while (fileStream1.Position == fileLength) { await fileStream1.ReadAsync(data, 0, 100); // 等待读取完成 // 这里可以处理读取到的data }
只有一种极端情况可以不用await:你明确知道这个异步操作的结果对你后续逻辑完全没影响,而且你已经做好了处理异常的准备(比如通过Task.ContinueWith),但这种场景非常罕见。
问题3:能否用Thread.Sleep替代await运算符?
绝对不可以!这完全是两种不同的东西,核心区别在阻塞 vs 非阻塞:
Thread.Sleep(n)是直接阻塞当前线程,让它什么都不干,硬等n毫秒——这会浪费线程资源,尤其是在高并发场景下,线程是宝贵的资源,阻塞线程会导致程序性能急剧下降。await则是非阻塞的:当你await一个Task时,当前线程会被释放,去处理其他任务(比如UI操作、其他请求),等异步操作完成后,再回到原来的位置继续执行。而且await能精准等待异步操作完成,不需要你猜要等多久(你根本不知道IO操作要花多长时间,Thread.Sleep的时间设置长了浪费,短了可能操作还没完成)。
举个例子,如果你用Thread.Sleep(1000)代替await fileStream.WriteAsync(...),你要么等太久,要么等不够,而且这段时间线程啥都干不了,完全是浪费。
内容的提问来源于stack exchange,提问作者Divya

