简体   繁体   English

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

[英].NET async webservice call with a callback

We have a legacy VB6 application that uses an ASMX webservice written in C# (.NET 4.5), which in turn uses a library (C#/.NET 4.5) to execute some business logic. 我们有一个遗留的VB6应用程序,它使用用C#(.NET 4.5)编写的ASMX Web服务,后者又使用库(C#/。NET 4.5)来执行一些业务逻辑。 One of the library methods triggers a long-running database stored procedure at the end of which we need to kick off another process that consumes the data generated by the stored procedure. 其中一个库方法触发了一个长时间运行的数据库存储过程,最后我们需要启动另一个消耗存储过程生成的数据的进程。 Because one of the requirements is that control must immediately return to the VB6 client after calling the webservice, the library method is async , takes an Action callback as a parameter, the webservice defines the callback as an anonymous method and doesn't await the results of the library method call. 因为其中一个要求是控件必须在调用webservice后立即返回VB6客户端,库方法是async ,将Action回调作为参数,webservice将回调定义为匿名方法而不await结果库方法调用。

At a high level it looks like this: 在高层次上,它看起来像这样:

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...
            }
        }
    }
}

The above works in local tests, but when the code is deployed as a webservice Step 2 is never executed even though the Step 1 stored procedure completes and generates the data. 以上工作在本地测试中,但是当代码部署为Web服务时,即使步骤1存储过程完成并生成数据,也不会执行步骤2

Any idea what we are doing wrong? 知道我们做错了什么吗?

it is dangerous to leave tasks running on IIS, the app domain may be shut down before the method completes, that is likely what is happening to you. 让任务在IIS上运行是危险的,应用程序域可能会在方法完成之前关闭,这很可能发生在你身上。 If you use HostingEnvironment.QueueBackgroundWorkItem you can tell IIS that there is work happening that needs to be kept running. 如果您使用HostingEnvironment.QueueBackgroundWorkItem您可以告诉IIS有正在进行的工作需要保持运行。 This will keep the app domain alive for a extra 90 seconds (by default) 这将使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...
            }
        }
    }
}

If you want something more reliable than 90 extra seconds see the article " Fire and Forget on ASP.NET " by Stephen Cleary for some other options. 如果你想要比90秒额外更可靠的东西,请参阅Stephen Cleary的文章“ Fire and Forget on ASP.NET ”以获取其他一些选项。

I have found a solution to my problem that involves the old-style (Begin/End) approach to asynchronous execution of code: 我找到了一个解决我的问题的方法,它涉及异步执行代码的旧式(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...
        }
    }

The onUpdateCompleted callback action is passed to the BeginExecuteNonQuery method as the second argument and is then consumed in the AsyncCallback (the first argument). onUpdateCompleted回调操作作为第二个参数传递给BeginExecuteNonQuery方法,然后在AsyncCallback (第一个参数)中使用。 This works like a charm both when debugging inside VS and when deployed to IIS. 这在VS内部调试和部署到IIS时都像魅力。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM