【问题标题】:Entity Framework query fails 'A task was canceled.'实体框架查询失败“任务已取消。”
【发布时间】:2022-01-15 03:39:35
【问题描述】:

我有一个 ASP Core 3.1 Web 项目,我想在其中添加 EntityFramework Core。
我创建了一个 db 上下文、具有数据库操作的模型类,并将其注入到我的主类(Azure Bot)中。

但是,当我尝试将记录插入数据库时​​,它总是失败并显示错误

System.Threading.Tasks.TaskCanceledException: '任务已取消。'

这是我的 startup.cs:

services.AddDbContext<IVRContext>(options =>
            options.UseSqlServer(
                Configuration.GetConnectionString("DefaultConnection"),
                optionBuilder => optionBuilder.EnableRetryOnFailure()
            )
        );
        services.AddTransient<IVRCallModel>();

这是我正在调用的 IVRModel 中的函数:

 public async Task InsertCallAsync(IVRCall call)
    {
        try
        {
            await _ivrContext.Calls.AddAsync(call);
            await _ivrContext.SaveChangesAsync();
        }
        catch (Exception ex)
        {
            throw new Exception(ex.Message, ex);
        }
    }

我是这样称呼它的:

private async Task NotificationProcessor_OnNotificationReceivedAsync(NotificationEventArgs args)
{
    this.GraphLogger.CorrelationId = args.ScenarioId;
    if (args.ResourceData is Call call)
    {
        if (call.Direction != CallDirection.Outgoing && call.ToneInfo == null)
        {
            if (args.ChangeType == ChangeType.Created && call.State == CallState.Incoming)
            {
                await SaveCall(call.Id, call.CallChainId, "Incoming");
                
                .... code removed 
            }
        }
    }
}

private async Task SaveCall(string callId, string callChainId, string callState, string redirectCallId = null)
{
    IVRCall newCall = new IVRCall();
    newCall.Id = callId;
    newCall.CallChainId = callChainId;
    newCall.TimeStamp = DateTime.Now.ToString("dd-MM-yyyy HH:mm:ss");
    newCall.State = callState;
    newCall.RedirectCallId = redirectCallId; 
    await _ivrCallModel.InsertCallAsync(newCall);
}

编辑: “原始” NoticicationProcessor_OnNotificationReceived 函数,它调用异步方法。 (来自 Microsoft 示例项目)

private void NotificationProcessor_OnNotificationReceived(NotificationEventArgs args)
{
    _ = NotificationProcessor_OnNotificationReceivedAsync(args).ForgetAndLogExceptionAsync(this.GraphLogger, $"Error processing notification {args.Notification.ResourceUrl} with scenario {args.ScenarioId}");
}

【问题讨论】:

  • throw new Exception(ex.Message, ex); 为什么?!您只是无缘无故地在基本 Exception 类型中包装了一个有意义的异常类型。只需删除try..catch 块,让异常正常传播。或者至少重新抛出原来的异常。
  • 能分享一下异常的堆栈跟踪吗?
  • System.Threading.Tasks.TaskCanceledException HResult=0x8013153B 消息=任务被取消。 Source=System.Private.CoreLib StackTrace: at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) 这就是它所显示的全部

标签: c# .net-core entity-framework-core


【解决方案1】:

当代码在响应已发送到客户端后尝试运行时,将发生该异常。当您使用 async/await 时,可能会在某处未等待异步的情况下发生这种情况。

确实,这里正在发生这种情况:

private void NotificationProcessor_OnNotificationReceived(NotificationEventArgs args)
{
    _ = NotificationProcessor_OnNotificationReceivedAsync(args).ForgetAndLogExceptionAsync(this.GraphLogger, $"Error processing notification {args.Notification.ResourceUrl} with scenario {args.ScenarioId}");
}

await 作用于不完整的Task 时,它实际上会返回。它返回一个自己的Task,以便调用者可以跟踪它何时完成。因此,当NotificationProcessor_OnNotificationReceivedAsync 返回时,工作实际上并未完成。而且因为您没有使用Task,所以会继续执行,ASP.NET 会包装请求并结束该请求的任务。

在异步代码中使用事件处理程序可能会很棘手,尤其是在这种情况下,您需要在工作完成之前暂停执行,否则您会得到意想不到的结果。您可以使用 .GetAwaiter().GetResult() 之类的东西在异步代码上同步等待,但这可能会产生意想不到的后果。

我认为你最好的办法是在这种情况下只使用同步代码:使用 .SaveChanges() 而不是 .SaveChangesAsync()

关于你使用.AddAsync()the documentation 说:

此方法是异步的,仅允许特殊值生成器(例如“Microsoft.EntityFrameworkCore.Metadata.SqlServerValueGenerationStrategy.SequenceHiLo”使用的值生成器)异步访问数据库。对于所有其他情况,应使用非异步方法。

所以你应该一直只使用.Add(),即使在异步方法中也是如此。

关于您的try/catch 块的评论也是有效的。如果您要捕获一个异常只是为了使用相同的消息抛出一个新异常,那么根本不要捕获该异常。即使您需要在再次抛出异常之前进行一些日志记录或其他操作,也请仅使用 throw; 而不是 throw new Exception(ex.Message, ex);。抛出新异常会更改异常发生位置的堆栈跟踪,并可能使调试变得困难。

【讨论】:

  • 我正在使用 Microsoft 的示例,您说得对,实际的 NotificationProcessor 是无效的。然后它调用异步函数。 (代码见上面的编辑)
  • 谢谢!这有帮助。 “任务已取消”错误已消失。只是现在我得到一个新的错误。线程中的第一个 db 调用不会发生这种情况,但后续调用不会发生这种情况: System.ObjectDisposedException: '无法访问已释放的上下文实例。此错误的常见原因是释放从依赖注入中解析的上下文实例,然后尝试在应用程序的其他地方使用相同的上下文实例。
  • 依赖注入创建了一个DbContext 对象,该对象在 HTTP 请求的整个生命周期内都存在。一旦响应返回给客户端,范围就结束并且DbContext 被释放。当您的代码使用您的 DbContext 在返回响应后运行并且 DbContext 已被释放时,就会发生该异常。
  • 要么弄清楚为什么它在响应已经发送后运行(你在某处使用Task.Run 吗?)并修复它,或者,如果它在响应已经发送后运行没关系,然后您可以创建一个新范围并在您的InsertCallAsync 方法中获取一个新的DbContext,如下所示:stackoverflow.com/a/48368934/1202807
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-08-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多