首页 文章

带有回调的.NET异步webservice调用

提问于
浏览
3

我们有一个遗留的VB6应用程序,它使用用C#(.NET 4.5)编写的ASMX Web服务,后者又使用库(C#/ .NET 4.5)来执行一些业务逻辑 . 其中一个库方法触发了一个长时间运行的数据库存储过程,最后我们需要启动另一个消耗存储过程生成的数据的进程 . 因为其中一个要求是控件必须在调用webservice后立即返回VB6客户端,库方法是 async ,将 Action 回调作为参数,webservice将回调定义为匿名方法而不是 await 结果库方法调用 .

从高层次来看,它看起来像这样:

using System;
using System.Data.SqlClient;
using System.Threading.Tasks;
using System.Web.Services;

namespace Sample
{
    [WebService(Namespace = "urn:Services")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    public class MyWebService
    {
        [WebMethod]
        public string Request(string request)
        {
            // Step 1: Call the library method to generate data
            var lib = new MyLibrary();
            lib.GenerateDataAsync(() =>
            {
                // Step 2: Kick off a process that consumes the data created in Step 1
            });

            return "some kind of response";
        }
    }

    public class MyLibrary
    {
        public async Task GenerateDataAsync(Action onDoneCallback)
        {
            try
            {
                using (var cmd = new SqlCommand("MyStoredProc", new SqlConnection("my DB connection string")))
                {
                    cmd.CommandType = System.Data.CommandType.StoredProcedure;
                    cmd.CommandTimeout = 0;
                    cmd.Connection.Open();

                    // Asynchronously call the stored procedure.
                    await cmd.ExecuteNonQueryAsync().ConfigureAwait(false);

                    // Invoke the callback if it's provided.
                    if (onDoneCallback != null)
                        onDoneCallback.Invoke();
                }
            }
            catch (Exception ex)
            {
                // Handle errors...
            }
        }
    }
}

以上工作在本地测试中,但是当代码部署为web服务时,即使 Step 1 存储过程完成并生成数据,也永远不会执行 Step 2 .

知道我们做错了什么吗?

2 回答

  • 1

    将任务保留在IIS上运行是危险的,应用程序域可能会在方法完成之前关闭,这很可能发生在您身上 . 如果您使用HostingEnvironment.QueueBackgroundWorkItem,您可以告诉IIS,正在进行的工作需要保持运行 . 这将使app域保持活动90秒(默认情况下)

    using System;
    using System.Data.SqlClient;
    using System.Threading.Tasks;
    using System.Web.Services;
    
    namespace Sample
    {
        [WebService(Namespace = "urn:Services")]
        [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
        public class MyWebService
        {
            [WebMethod]
            public string Request(string request)
            {
                // Step 1: Call the library method to generate data
                var lib = new MyLibrary();
                HostingEnvironment.QueueBackgroundWorkItem((token) =>
                    lib.GenerateDataAsync(() =>
                    {
                        // Step 2: Kick off a process that consumes the data created in Step 1
                    }));
    
                return "some kind of response";
            }
        }
    
        public class MyLibrary
        {
            public async Task GenerateDataAsync(Action onDoneCallback)
            {
                try
                {
                    using (var cmd = new SqlCommand("MyStoredProc", new SqlConnection("my DB connection string")))
                    {
                        cmd.CommandType = System.Data.CommandType.StoredProcedure;
                        cmd.CommandTimeout = 0;
                        cmd.Connection.Open();
    
                        // Asynchronously call the stored procedure.
                        await cmd.ExecuteNonQueryAsync().ConfigureAwait(false);
    
                        // Invoke the callback if it's provided.
                        if (onDoneCallback != null)
                            onDoneCallback();
                    }
                }
                catch (Exception ex)
                {
                    // Handle errors...
                }
            }
        }
    }
    

    如果你想要比90秒额外更可靠的东西,请参阅Stephen Cleary的文章“Fire and Forget on ASP.NET”以获得其他选项 .

  • 2

    我找到了一个解决我的问题的方法,它涉及异步执行代码的旧式(Begin / End)方法:

    public void GenerateData(Action onDoneCallback)
        {
            try
            {
                var cmd = new SqlCommand("MyStoredProc", new SqlConnection("my DB connection string"));
                cmd.CommandType = System.Data.CommandType.StoredProcedure;
                cmd.CommandTimeout = 0;
                cmd.Connection.Open();
    
                cmd.BeginExecuteNonQuery(
                    (IAsyncResult result) =>
                    {
                        cmd.EndExecuteNonQuery(result);
                        cmd.Dispose();
    
                        // Invoke the callback if it's provided, ignoring any errors it may throw.
                        var callback = result.AsyncState as Action;
                        if (callback != null)
                            callback.Invoke();
                    },
                    onUpdateCompleted);
            }
            catch (Exception ex)
            {
                // Handle errors...
            }
        }
    

    onUpdateCompleted 回调操作作为第二个参数传递给 BeginExecuteNonQuery 方法,然后在 AsyncCallback (第一个参数)中使用 . 这在VS内部调试和部署到IIS时都像魅力 .

相关问题