Try and catch语句不工作,捕获奇怪的错误



在前端,我有一个SPA,通过MS图形API调用检索电子邮件数据和附件。附件是base64格式的,用户选择他们想要选择的电子邮件,然后将这些数据重新加载到数据库中,并且文件也通过MS Graph API再次从List<IFormfile>上传到oneddrive。我遇到了一个断断续续发生的奇怪问题。基本上,支持文件没有上传到oneddrive,我的try catch似乎没有捕捉到任何错误来说明原因。当我遍历代码时,有时它通过了,有时它似乎在随机点上被抵消了。我可以使用任务和异步不正确吗?

private async Task UploadSupportingDocs(int ID, List<IFormFile> supportingDocs,
string supportingDocsJSON)
{
Console.WriteLine($"Upload Supporting Docs");
if (supportingDocs != null || supportingDocs.Count > 0)
{
IEnumerable<DocumentUploads> documents;
using (var connection = new SqlConnection(connectionString))
{
//Use returned ID from previous query and save supporting documents
var querySupport = "exec dbo.A2P_Edit_0136_RR_UploadSupportingDocs_QA"
+ " @supportingDocsJSON, @ID, @UserID";
documents = connection.Query<DocumentUploads>(querySupport,
new { supportingDocsJSON = supportingDocsJSON, ID = ID,
UserID = UserID });
}
var tasks = documents.Select(async file => {
try {
var filePath = Path.Combine(fileDirectory, file.DOCUMENT_PATH);
var fileName = file.DOCUMENT_FILENAME;
var fullPath = Path.Combine(filePath, fileName);
var supportingDoc = supportingDocs.Where(item => item.FileName
.Equals(file.FILENAME_AT_UPLOAD,
StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
using (var connection = new SqlConnection(connectionString))
{
using (var memoryStream = new MemoryStream())
{
await supportingDoc.CopyToAsync(memoryStream);
memoryStream.Position = 0;
var UploadResponse = await oneDrive
.UploadFileStreamAsync(SharedDriveID,
memoryStream, fullPath, "replace");
var SharedLinkResponse = await oneDrive
.DriveItemLinksAsync(SharedDriveID, fullPath,
"path", "users", "edit");
connection.Query("exec dbo.SomeQuery @TAB_ID,
@DriveItemID, @WebURL, @EditLink", new
{
TAB_ID = file.TAB_ID,
DriveItemID = UploadResponse.Id,
WebURL = UploadResponse.WebUrl,
EditLink = SharedLinkResponse.Link.WebUrl,
});
}
}
}
catch(Exception ex)
{
Console.WriteLine($"File Upload Error: {ex}");
}
});
Console.WriteLine($"Upload Supporting Docs Complete");
await Task.WhenAll(tasks);
}
}

只是为了更多地了解发生了什么。在前端,我们还发送一些带有文件名的JSON数据,这样我们就可以一次性将其保存在数据库中。然后在我的异步linq查询中,它需要上传的文件名,并在List<IFormFile>中找到相关的supportingDoc,然后将其上传到onedrive,然后将一些额外的东西保存到数据库中,如生成的id等。这是没有道理的,为什么这样做有时会失败,而其他人,即使是相同的文档。这些文件还从数据库调用中获得一个新的唯一名称,即onedrive函数的fullPath。该函数也给出如下:

public async Task<DriveItem> UploadFileStreamAsync(string DriveID,
MemoryStream SourceFile, string DestinationPath, string ConflictChoice)
{
var tries = 0;
var maxRetries = 1;
DriveItem response = null;
while (tries <= maxRetries)
{
tries++;
try
{
using var stream = SourceFile;
// Use properties to specify the conflict behavior
// in this case, replace
var uploadProps = new DriveItemUploadableProperties
{
AdditionalData = new Dictionary<string, object>
{
{ "@microsoft.graph.conflictBehavior", ConflictChoice}
}
};
// Create the upload session
// itemPath does not need to be a path to an existing item
var uploadSession = await _graphServiceClient.Me.Drives[DriveID].Root
.ItemWithPath(DestinationPath)
.CreateUploadSession(uploadProps)
.Request()
.PostAsync();
// Max slice size must be a multiple of 320 KiB
int maxSliceSize = 320 * 1024;
var fileUploadTask = new LargeFileUploadTask<DriveItem>(
uploadSession, stream, maxSliceSize);
var totalLength = stream.Length;
// Create a callback that is invoked after each slice is uploaded
IProgress<long> progress = new Progress<long>(prog => {
Console.WriteLine($"Uploaded {prog} bytes of {totalLength} bytes");
});
// Upload the file
var uploadResult = await fileUploadTask.UploadAsync(progress);
if (uploadResult.UploadSucceeded)
{
response = uploadResult.ItemResponse;
Console.WriteLine(uploadResult.UploadSucceeded ?
$"Upload complete, item ID: {uploadResult.ItemResponse.Id}" :
"Upload failed");
}
tries = maxRetries + 1;
}
catch (ServiceException svcex) when (svcex.Message.Contains(
"Continuous access evaluation resulted in claims challenge"))
{
try
{
Console.WriteLine($"{svcex}");
string claimChallenge = WwwAuthenticateParameters
.GetClaimChallengeFromResponseHeaders(svcex.ResponseHeaders);
_consentHandler.ChallengeUser(initialScopes, claimChallenge);
}
catch (Exception ex2)
{
_consentHandler.HandleException(ex2);
}
}
}
return response;
}

总的来说,我很困惑代码如何不工作,但我没有捕获任何错误。

oneddrive变量在program.cs中定义为:

builder.Services.AddTransient<IOneDrive, OneDrive>();

使用依赖注入在BaseController类中启动,如下所示:

// GET: HomeController
protected string connectionString;
protected string fileDirectory;
protected string UserID;
protected string locationOfLibreOfficeSoffice;
protected readonly IFileProvider _fileProvider;
protected readonly IOneDrive oneDrive;
protected readonly IEmailService emailService;
protected readonly string SharedDriveID;
protected readonly IEnumerable<string> initialScopes;
protected readonly string SignedOutRedirectUri;
public BaseController(IConfiguration configuration, IWebHostEnvironment env,
IHttpContextAccessor accessor, IOneDrive OneDrive, IEmailService EmailService)
{
connectionString = configuration["ConnectionStrings:DBContext"];
fileDirectory = "/files";
UserID = accessor.HttpContext.User.FindFirstValue("UserID");
oneDrive = OneDrive;
emailService = EmailService;
SharedDriveID = configuration["AzureAd:SharedDriveID"];
initialScopes = configuration.GetValue<string>("DownstreamApi:Scopes")?.Split(' ');
SignedOutRedirectUri = configuration["AzureAd:SignedOutRedirectUri"];
}

这里有很多代码,在不同级别上有很多异常处理,可以阻止异常的传播。我将提请您注意一个潜在的故障点:

IEnumerable<DocumentUploads> documents;
using (var connection = new SqlConnection(connectionString))
{
//...
documents = connection.Query<DocumentUploads>(/* ... */);
}
var tasks = documents.Select(async file => { /* ... */ });

documents序列被枚举时,底层的connection已经被处理掉了。因此,除非connection.Query方法返回一个已经物化的集合,否则我预计这里会发生异常。要解决这个问题,可以推迟connection的处理,直到documents被完全枚举,或者在退出using块之前实现该序列:

DocumentUploads[] documents;
using (var connection = new SqlConnection(connectionString))
{
//...
documents = connection.Query<DocumentUploads>(/* ... */).ToArray();
}

你的oneDrive变量很可能是一个不线程安全的类的实例。

它在你所有的任务之间共享,你的代码并行运行。所以你有线程并发问题,并且有很少的选项来修复它:

  1. 不要并行运行多个线程共享该变量。
  2. 或者停止使用oneDrive作为注入依赖,但是为每个任务实例化一个,就像你已经在sql连接中做的那样。
  3. 或者尝试使依赖项线程安全。

最后一个命题比前面两个命题难多了。为了响应Web应用程序的单个请求而并行运行许多任务通常是一个坏主意:这会耗尽线程池并阻止扩展。

因此,最好修改代码以停止并行运行任务。

只需用foreach迭代您的集合,等待必须等待的内容。不要建立一个最终并行运行的任务列表。

当你在评论中要求时,将你的依赖切换为单例会使问题更糟:它会在并发请求之间共享到你的SPA,当多个用户正在使用你的应用程序时,会导致更多的线程并发问题。

顺便说一下,似乎你使用同步数据库查询,连接打开,…

最新更新