带回调的.NET异步web服务调用

我们有一个传统的VB6应用程序,它使用用C#(.NET 4.5)编写的ASMX webservice,后者又使用库(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服务时,即使第1步存储过程完成并生成数据, 第2步也不会执行。

任何想法我们做错了什么?


将任务保留在IIS上运行是非常危险的,应用程序域可能会在方法完成之前关闭,这可能是您正在发生的事情。 如果您使用HostingEnvironment.QueueBackgroundWorkItem ,则可以告诉IIS有需要继续运行的工作。 这将使应用程序域保持额外的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”以获取其他一些选择。


我已经找到了解决我的问题的方法,它涉及到异步执行代码的旧式(开始/结束)方法:

    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时都很有用。

链接地址: http://www.djcxy.com/p/56665.html

上一篇: .NET async webservice call with a callback

下一篇: Portname and Servicename are not getting changed in Java WebService