Task.WhenAny 的陷阱
最近做的一个下载程序经常会陷入死循环,一直下载,未报异常,但就是下载的文件不存在。
public async Task<bool> DownloadFileTimeout(string fileUrl, string savePath) { var timeoutTask = Task.Delay(this.Timeout); // 调用下载文件的异步方法 var downloadTask = DownloadFileTaskAsync(fileUrl, savePath); // 等待第一个完成的任务(下载或超时) var completedTask = await Task.WhenAny(downloadTask, timeoutTask); if (completedTask == downloadTask) { return true; } else { return false; } }
public async Task DownloadFileTaskAsync(string fileUrl, string savePath) { // 发送 GET 请求 HttpResponseMessage response = await this.GetAsync(fileUrl, HttpCompletionOption.ResponseHeadersRead); // 确保请求成功 response.EnsureSuccessStatusCode(); // 获取文件流 using (var stream = await response.Content.ReadAsStreamAsync()) { // 创建文件流用于保存文件 using (var fileStream = new FileStream(savePath, FileMode.Create, FileAccess.Write, FileShare.None)) { // 将内容写入文件 await stream.CopyToAsync(fileStream); } } }
DeepSeek 分析如下:
原因解释: Task.WhenAny 的陷阱 即使 downloadTask 因异常而失败,它仍会先于 timeoutTask 完成,导致 completedTask == downloadTask 为 true。但此时 downloadTask 可能处于 Faulted 状态。 检查任务的实际结果 通过 await downloadTask 重新抛出潜在异常,并用 try/catch 捕获。若下载成功(无异常),返回 true;若抛出异常(如 HTTP 错误),返回 false。 正确处理超时与错误 确保只有下载真正成功时返回 true,其他情况(超时或 HTTP 错误)均返回 false
修改后代码如下:
public async Task<bool> DownloadFileTimeout(string fileUrl, string savePath) { var timeoutTask = Task.Delay(this.Timeout); var downloadTask = DownloadFileTaskAsync(fileUrl, savePath); var completedTask = await Task.WhenAny(downloadTask, timeoutTask); if (completedTask == downloadTask) { try { await downloadTask; // 触发可能的异常 return true; } catch { return false; // 下载失败时返回 false } } else { return false; // 超时返回 false } }
也就是说异步方法,很多异常并不能总是在其调用链父级被捕获到。如果想抛出异常,还得自己接力。
桂棹兮兰桨,击空明兮溯流光。
浙公网安备 33010602011771号