通过Web服务异步执行长时间运行的存储过程

时间:2016-03-13 16:41:26

标签: c# asynchronous async-await

我有一个SQL Server存储过程负责处理大量数据,需要15秒到几分钟才能运行。我正在尝试使用来自桌面UI客户端的请求调用的异步MVC操作异步执行它。我似乎完全按照.NET异步书籍和教程的方式进行操作,但似乎存储过程在启动后不久就会中止。

启动时,存储过程会向状态表中添加一条记录,以指示正在进行的作业。成功完成后,存储过程将使用成功标志更新状态记录,并且任何错误都由TRY-CATCH块处理,该块使用错误标志更新状态记录。

这已经在SQL Server Management Studio中进行了全面测试,所以我确信存储过程不能只是悄悄地炸弹。但是,当通过异步调用执行存储过程时(见下文),作业记录将插入状态表中,但它不会更新,这意味着存储过程永远不会到达其结束并在中期终止-flight,可能是由于数据库连接关闭。

存储过程调用包含在此异步存储库方法中:

    public async Task ProcessCatalogUpdateAsync(Guid updateID)
    {
        try
        {
            using (var cmd = new SqlCommand("ProcessCatalogUpdate", new SqlConnection(_connectionString)))
            {
                cmd.CommandType = CommandType.StoredProcedure;
                cmd.CommandTimeout = 0;
                cmd.Parameters.Add("@UpdateID", SqlDbType.UniqueIdentifier).Value = updateID;

                cmd.Connection.Open();        

                await cmd.ExecuteNonQueryAsync().ConfigureAwait(false);
            }
        }
        catch (Exception ex)
        {
            var message = String.Format("Error processing catalog update ID={0}", updateID);
            throw new DataException(message, ex);
        }
    }

控制器操作如下所示:

    public async Task UpdateCatalog(string updateID)
    {
        var uid = Guid.Empty; // breakpoint here
        if (!Guid.TryParse(updateID, out uid))
            throw new Exception("Bad UpdateID in request!");

        await _repository.ProcessCatalogUpdateAsync(uid);

        System.Diagnostics.Trace.WriteLine("Catalog update completed"); // breakpoint here
    }

我已经以简单的HTML页面的形式构建了一个测试工具,它使用jQuery异步调用该操作,如下所示:

    var requestURL = "/catalog/updateCatalog?updateID=" + $("#updateID").val();

    $.ajax({
        url: requestURL,
        type: "GET",
        error: function (xhr, status, errorText) {
            alert("Error: " + errorText); // breakpoint here
        },
        success: function () {
            alert("Call returned!"); // breakpoint here
        }
    });

我在JavaScript中的两个警报以及控制器操作方法的开头都设置了断点,以确认操作是否实际执行。我在System.Diagnostics调用存储库方法之后立即在await行处有一个断点,以便在方法返回时捕获(尽管在调试会话中它可能毫无意义,因为线程上下文很可能是不同)。

这就是:

  • 点击UpdateCatalog操作开头的断点
  • 存储过程将新行插入状态表
  • success JavaScript处理程序永远不会触发
  • 状态表永远不会使用成功或错误标志更新
  • 永远不会遇到System.Diagnostics处的断点

我做错了什么?

更新

事实证明,存储过程代码本身存在一个问题,即最终做了一些需要20多分钟才能完成的事情。我现在已经将它重写为快几个数量级。但是,异步的东西仍然没有像我期望的那样工作。只有控制器操作方法在success行之后而不是立即恢复后,jQuery AJAX await事件才会触发。

我应该如何更改我的控制器操作方法以执行真正的"“即发即忘”"方式是什么?

1 个答案:

答案 0 :(得分:2)

await不会启动工作。它等待已经运行的工作。因此,等待并不是一种发起“一劳永逸”的手段。

更改

await _repository.ProcessCatalogUpdateAsync(uid);

_repository.ProcessCatalogUpdateAsync(uid);

将诊断日志移到ProcessCatalogUpdateAsync(或包装方法)中。现在这已经过去了,忘记了。

"开火,忘记"有它的危险。这项工作可能会在没有通知的情况下丢失,或者如果启动太快,并发火灾和忘记工作可能会增加并使服务过载。