CodeGym /课程 /C# SELF /在异步操作中的错误处理 ( async/ await)

在异步操作中的错误处理 ( async/ await)

C# SELF
第 42 级, 课程 3
可用

1. 介绍

先从一个重要的问题开始:为什么不能单纯信任异步方法并希望它们总是顺利完成?对文件的操作经常会抛出异常——文件可能被删除,磁盘空间用完,权限不足或者文件被锁住。在同步代码里你会在熟悉的 try-catch 块里捕获这些问题。异步代码的哲学是一样的,但有细微差别:错误可能不会在调用方法时立即发生,而是在操作实际执行的时候发生。

错误发生的时刻

在同步代码里,通过 StreamReader.Read() 读取时异常会直接在调用那一行抛出——你在 catch 里捕获它,这样很好。
在异步代码中(await stream.ReadAsync())错误不会在操作启动时发生,而是在你对任务做 await 时——任务以错误结束的时候。如果忘了写 await,错误可能在一段时间内对你“不可见”。

2. 如何在异步方法里捕获异常

先看一个常见的模板:


try
{
    using FileStream fs = new FileStream("myfile.txt", FileMode.Open);
    byte[] buffer = new byte[1024];
    int bytesRead = await fs.ReadAsync(buffer, 0, buffer.Length);
    // 进一步处理...
}
catch (IOException ex)
{
    Console.WriteLine("输入输出错误: " + ex.Message);
}
catch (UnauthorizedAccessException ex)
{
    Console.WriteLine("没有访问文件的权限: " + ex.Message);
}
catch (Exception ex)
{
    Console.WriteLine("未知错误: " + ex);
}

对,就是用熟悉的 try-catch,但在异步方法里。重要的是方法本身要有关键字 async,否则编译器会抱怨。

重要细节:把 await 放在哪里


Task<int> readTask = fs.ReadAsync(buffer, 0, buffer.Length);
// ... 在这里意外地忘了 await 或者处理

在这种情况下,如果发生错误,它会进入任务本身(Task),你不会知道,直到你尝试获取结果——比如通过 await readTask 或者查看 Task.Exception。如果你完全忘记了 await ——任务可能以错误结束,但没人会告诉你。

3. 为什么不处理错误会让异步代码变得狡猾

场景 1: «Fire and forget» — 新手的陷阱


FileStream fs = new FileStream("file.txt", FileMode.Open);
byte[] buffer = new byte[8000];
fs.ReadAsync(buffer, 0, buffer.Length);
// 然后程序继续自己的事情

读取操作会去“并行跑”,如果它以错误结束,没有任何 catch 能捕获它。异常会藏在任务里。这个模式叫「fire and forget」,在真实应用中可能导致关键错误丢失和资源泄露。

场景 2: 异步方法没有 await


Task t = MyAsyncMethod();
// ... 做点其他事,然后忘了 t

在 MyAsyncMethod 里发生的错误不会冒出来,除非你显式等待那个任务(await t 或者 t.Wait())。

4. 如何正确捕获来自异步任务的错误

策略 1: 始终使用 await


try
{
    await SomeFileOperationAsync();
}
catch (Exception ex)
{
    Console.WriteLine("出问题了: " + ex.Message);
}

这样异常会在等待点直接抛出,不会丢失。

策略 2: 用 .ContinueWith 来处理

如果因为某些原因你不想用 await,可以通过 ContinueWith 添加错误处理:


var task = fs.ReadAsync(buffer, 0, buffer.Length);
task.ContinueWith(t =>
{
    if (t.Exception != null)
        Console.WriteLine("异步读取时出错: " + t.Exception.InnerException);
}, TaskContinuationOptions.OnlyOnFaulted);

坦白说,在现代 C# 应用中很少这样做——async/await 使代码更简单、更清晰。

异步文件操作时可能遇到的异常

5. 示例:带错误处理的异步读取

把这些逻辑加到你的程序里:


using System;
using System.IO;
using System.Threading.Tasks;

namespace FileAsyncDemo
{
    class Program
    {
        static async Task Main()
        {
            string path = "bigfile.txt";
            byte[] buffer = new byte[4096];

            try
            {
                using FileStream fs = new FileStream(path, FileMode.Open);
                int bytesRead = await fs.ReadAsync(buffer, 0, buffer.Length);
                Console.WriteLine($"已从 {path} 读取 {bytesRead} 字节");
            }
            catch (FileNotFoundException ex)
            {
                Console.WriteLine("未找到文件: " + ex.Message);
            }
            catch (UnauthorizedAccessException ex)
            {
                Console.WriteLine("没有文件访问权限: " + ex.Message);
            }
            catch (IOException ex)
            {
                Console.WriteLine("读/写错误: " + ex.Message);
            }
            catch (Exception ex)
            {
                Console.WriteLine("其他错误: " + ex.Message);
            }
        }
    }
}

重要提示

如果在 Main 中你不使用 await,而只是做 Task result = SomeAsyncMethod(); ——错误会“静默”存在,直到你真正尝试获取结果时才显现。

6. 最常见的错误和踩坑

忘记对异步方法使用 await ——错误不会及时冒出来,程序行为变得不可预测。

不把异步调用包在 try-catch 中 ——应用在第一次失败时就崩溃。

只捕获 Exception,忽略像 UnauthorizedAccessException 或 OperationCanceledException 这些特定异常 ——诊断变差。

使用「fire and forget」任务而不记录错误 ——异常会丢失在 Task 里。

以为任务完成就表示成功。需要显式等待结果(await)并处理异常。

2
任务
C# SELF, 第 42 级, 课程 3
已锁定
读取文件时的错误处理
读取文件时的错误处理
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION