如果并行进行多个DownloadToAsync调用,如何捕获RequestFailedException



我有一些这样的代码:

public async Task<IEnumerable<Response>> SaveFiles(IEnumerable<string> filePaths)
{
var baseFolder = "C:\Temp";
var fullTempPath = $"{baseFolder}\{Guid.NewGuid()}";
var fileDirectory = Directory.CreateDirectory(fullTempPath);
Task<Response>[] tasks = new Task<Response>[filePaths.Count()];
for (int i = 0; i < filePaths.Count(); i++)
{
var blobClientDetails = GetBlobClientDetails(filePaths.ElementAt(i));
var credentials = new AzureSasCredential(blobClientDetails.Value);
var blob = new BlobClient(blobClientDetails.Key, credentials);
tasks[i] = blob.DownloadToAsync(fileDirectory.FullName);
}
return await Task.WhenAll(tasks);
}
private KeyValuePair<Uri, string> GetBlobClientDetails(string filePath)
{
var filePathExcludingSAS = filePath.Substring(0, filePath.IndexOf('?'));
var sasToken = filePath.Substring(filePath.IndexOf('?') + 1);
return new KeyValuePair<Uri, string>(new Uri(filePathExcludingSAS), sasToken);
}

理论上,这将在等待前一个文件的下载完成之前触发每个文件的下载,因此最后是Task.WhenAll(tasks)。然而,我希望能够准确地捕捉异常,并能够指定下载失败的文件。我该怎么做?

LINQSelect运算符使将本机任务投影到具有一些额外功能的自定义任务变得容易。在这种情况下,额外的功能是将RequestFailedException封装在自定义异常类型中:

public async Task<Response[]> SaveFiles(IEnumerable<string> filePaths)
{
var baseFolder = "C:\Temp";
var fullTempPath = $"{baseFolder}\{Guid.NewGuid()}";
var fileDirectory = Directory.CreateDirectory(fullTempPath);
Task<Response>[] tasks = filePaths.Select(async filePath =>
{
var blobClientDetails = GetBlobClientDetails(filePath);
var credentials = new AzureSasCredential(blobClientDetails.Value);
var blob = new BlobClient(blobClientDetails.Key, credentials);
try
{
return await blob.DownloadToAsync(fileDirectory.FullName);
}
catch (RequestFailedException ex)
{
throw new FileDownloadException(ex.Message, ex) { FilePath = filePath };
}
}).ToArray();
Task<Response[]> whenAll = Task.WhenAll(tasks);
try { return await whenAll; }
catch { whenAll.Wait(); throw; } // Propagate AggregateException
}

FileDownloadException类:

public class FileDownloadException : Exception
{
public string FilePath { get; init; }
public FileDownloadException(string message, Exception innerException)
: base(message, innerException) { }
}

然后您可以使用SaveFiles方法,如下所示:

try
{
Response[] responses = await SaveFiles(filePaths);
}
catch (AggregateException aex)
{
foreach (var ex in aex.InnerExceptions)
{
if (ex is FileDownloadException fileEx)
{
// Our special exception wrapper
Console.WriteLine($"{fileEx.FilePath} => {fileEx.InnerException.Message}");
}
else
{
// Something else happened (not a RequestFailedException)
Console.WriteLine($"{ex.Message}");
}
}
}

感谢西奥多迄今为止的帮助。我已经创建了这样的东西,它目前还没有经过测试,我想知道它是否真的是异步的。

public async Task<IEnumerable<DownloadResult>> SaveFiles(IEnumerable<string> filePaths)
{
var baseFolder = ConfigurationManager.AppSettings["tempFolderLocation"];
var fullTempPath = $"{baseFolder}\{Guid.NewGuid()}";
var fileDirectory = Directory.CreateDirectory(fullTempPath);
var tasks = filePaths.Select(fp => DownloadFile(fp, fileDirectory.FullName));
return await Task.WhenAll(tasks);
}

我添加了一个"DownloadFile"方法,现在它为每个文件执行此操作,并返回一个任务:

private async Task<DownloadResult> DownloadFile(string filePath, string directory)
{
var blobClientDetails = GetBlobClientDetails(filePath);
var credentials = new AzureSasCredential(blobClientDetails.Value);
var blob = new BlobClient(blobClientDetails.Key, credentials);
try
{
var response = await blob.DownloadToAsync(directory);
return new DownloadResult(true, response);
}
catch (RequestFailedException ex)
{
// do some additional logging of the exception.
return new DownloadResult(false, null, ex.Message);
}
}

下载结果如下:

public class DownloadResult
{
public DownloadResult(bool isSuccessful, Response response, string errorMessage = "")
{
IsSuccessful = isSuccessful;
Response = response;
ErrorMessage = errorMessage;
}
public bool IsSuccessful { get; set; }
public Response Response { get; set; }
public string ErrorMessage { get; set; }
}

最新更新