使用 IHttpAsyncHandler 异步调用 WebService

发布于 2024-11-15 17:40:52 字数 2641 浏览 3 评论 0原文

这是基本设置。我们有一个 ASP.Net WebForms 应用程序,其中的页面包含需要访问外部 Web 服务的 Flash 应用程序。由于(我认为是安全性)Flash 的限制(不要问我,我根本不是 Flash 专家),我们无法直接从 Flash 连接到 Web 服务。解决方法是在 ASP.Net 中创建 Flash 应用程序将调用的代理,该代理将依次调用 WebService 并将结果转发回 Flash 应用程序。

不过,该网站的流量非常高,问题是,如果 Web 服务完全挂起,那么 ASP.Net 请求线程将开始备份,这可能会导致严重的线程匮乏。为了解决这个问题,我决定使用 IHttpAsyncHandler 正是为此目的而设计的。在其中,我将使用 WebClient 异步调用 Web 服务并将响应转发回来。网上关于如何正确使用 IHttpAsyncHandler 的示例很少,所以我只是想确保我没有做错。我的使用基于此处显示的示例: http://msdn.microsoft .com/en-us/library/ms227433.aspx

这是我的代码:

internal class AsynchOperation : IAsyncResult
{
    private bool _completed;
    private Object _state;
    private AsyncCallback _callback;
    private readonly HttpContext _context;

    bool IAsyncResult.IsCompleted { get { return _completed; } }
    WaitHandle IAsyncResult.AsyncWaitHandle { get { return null; } }
    Object IAsyncResult.AsyncState { get { return _state; } }
    bool IAsyncResult.CompletedSynchronously { get { return false; } }

    public AsynchOperation(AsyncCallback callback, HttpContext context, Object state)
    {
        _callback = callback;
        _context = context;
        _state = state;
        _completed = false;
    }

    public void StartAsyncWork()
    {
        using (var client = new WebClient())
        {
            var url = "url_web_service_url";
            client.DownloadDataCompleted += (o, e) =>
            {
                if (!e.Cancelled && e.Error == null)
                {
                    _context.Response.ContentType = "text/xml";
                    _context.Response.OutputStream.Write(e.Result, 0, e.Result.Length);
                }
                _completed = true;
                _callback(this);
            };
            client.DownloadDataAsync(new Uri(url));
        }
    }
}

public class MyAsyncHandler : IHttpAsyncHandler
{
    public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
    {
        var asynch = new AsynchOperation(cb, context, extraData);
        asynch.StartAsyncWork();
        return asynch;
    }

    public void EndProcessRequest(IAsyncResult result)
    {
    }

    public bool IsReusable
    {
        get { return false; }
    }

    public void ProcessRequest(HttpContext context)
    {
    }
}

现在这一切都有效,我认为它应该可以解决问题,但我不是 100% 当然。另外,创建我自己的 IAsyncResult 似乎有点矫枉过正,我只是想知道是否有一种方法可以利用从 Delegate.BeginInvoke 返回的 IAsyncResult 或其他东西。欢迎任何反馈。谢谢!!

Here's the basic setup. We have an ASP.Net WebForms application with a page that has a Flash application that needs to access an external Web Service. Due to (security I presume) limitations in Flash (don't ask me, I'm not a Flash expert at all), we can't connect to the Web Service directly from Flash. The work around is to create a proxy in ASP.Net that the Flash application will call, which will in turn call the WebService and forward the results back to the Flash application.

The WebSite has very high traffic though, and the issue is, if the Web Service hangs at all, then the ASP.Net request threads will start backing up which could lead to serious thread starvation. In order to get around that, I've decided to use an IHttpAsyncHandler which was designed for this exact purpose. In it, I'll use a WebClient to asynchronously call the Web Service and the forward the response back. There are very few samples on the net on how to correctly use the IHttpAsyncHandler, so I just want to make sure I'm not doing it wrong. I'm basing my useage on the example show here: http://msdn.microsoft.com/en-us/library/ms227433.aspx

Here's my code:

internal class AsynchOperation : IAsyncResult
{
    private bool _completed;
    private Object _state;
    private AsyncCallback _callback;
    private readonly HttpContext _context;

    bool IAsyncResult.IsCompleted { get { return _completed; } }
    WaitHandle IAsyncResult.AsyncWaitHandle { get { return null; } }
    Object IAsyncResult.AsyncState { get { return _state; } }
    bool IAsyncResult.CompletedSynchronously { get { return false; } }

    public AsynchOperation(AsyncCallback callback, HttpContext context, Object state)
    {
        _callback = callback;
        _context = context;
        _state = state;
        _completed = false;
    }

    public void StartAsyncWork()
    {
        using (var client = new WebClient())
        {
            var url = "url_web_service_url";
            client.DownloadDataCompleted += (o, e) =>
            {
                if (!e.Cancelled && e.Error == null)
                {
                    _context.Response.ContentType = "text/xml";
                    _context.Response.OutputStream.Write(e.Result, 0, e.Result.Length);
                }
                _completed = true;
                _callback(this);
            };
            client.DownloadDataAsync(new Uri(url));
        }
    }
}

public class MyAsyncHandler : IHttpAsyncHandler
{
    public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
    {
        var asynch = new AsynchOperation(cb, context, extraData);
        asynch.StartAsyncWork();
        return asynch;
    }

    public void EndProcessRequest(IAsyncResult result)
    {
    }

    public bool IsReusable
    {
        get { return false; }
    }

    public void ProcessRequest(HttpContext context)
    {
    }
}

Now this all works, and I THINK it should do the trick, but I'm not 100% sure. Also, creating my own IAsyncResult seems a bit overkill, I'm just wondering if there's a way I can leverage the IAsyncResult returned from Delegate.BeginInvoke, or maybe something else. Any feedback welcome. Thanks!!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

落花随流水 2024-11-22 17:40:52

哇,是的,如果您使用 .NET 4.0,通过利用任务并行库,您可以使这变得更容易/更干净。检查一下:

public class MyAsyncHandler : IHttpAsyncHandler
{
    public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
    {
        // NOTE: the result of this operation is void, but TCS requires some data type so we just use bool
        TaskCompletionSource<bool> webClientDownloadCompletionSource = new TaskCompletionSource<bool>();

        WebClient webClient = new WebClient())
        HttpContext currentHttpContext = HttpContext.Current;

        // Setup the download completed event handler
        client.DownloadDataCompleted += (o, e) =>
        {
            if(e.Cancelled)
            {
                // If it was canceled, signal the TCS is cacnceled
                // NOTE: probably don't need this since you have nothing canceling the operation anyway
                webClientDownloadCompletionSource.SetCanceled();
            }
            else if(e.Error != null)
            {
                // If there was an exception, signal the TCS with the exception
                webClientDownloadCompletionSource.SetException(e.Error);
            }
            else
            {
                // Success, write the response
                currentHttpContext.Response.ContentType = "text/xml";
                currentHttpContext.Response.OutputStream.Write(e.Result, 0, e.Result.Length);

                // Signal the TCS that were done (we don't actually look at the bool result, but it's needed)
                taskCompletionSource.SetResult(true);
            }
        };

        string url = "url_web_service_url";

        // Kick off the download immediately
        client.DownloadDataAsync(new Uri(url));

        // Get the TCS's task so that we can append some continuations
        Task webClientDownloadTask = webClientDownloadCompletionSource.Task;

        // Always dispose of the client once the work is completed
        webClientDownloadTask.ContinueWith(
            _ =>
            {
                client.Dispose();
            },
            TaskContinuationOptions.ExecuteSynchronously);

        // If there was a callback passed in, we need to invoke it after the download work has completed
        if(cb != null)
        {
            webClientDownloadTask.ContinueWith(
               webClientDownloadAntecedent =>
               {
                   cb(webClientDownloadAntecedent);
               },
               TaskContinuationOptions.ExecuteSynchronously);
         }

        // Return the TCS's Task as the IAsyncResult
        return webClientDownloadTask;
    }

    public void EndProcessRequest(IAsyncResult result)
    {
        // Unwrap the task and wait on it which will propagate any exceptions that might have occurred
        ((Task)result).Wait();
    }

    public bool IsReusable
    {
        get 
        { 
            return true; // why not return true here? you have no state, it's easily reusable!
        }
    }

    public void ProcessRequest(HttpContext context)
    {
    }
}

Wow, yeah you can make this a lot easier/cleaner if you're on .NET 4.0 by leveraging the Task Parallel Library. Check it:

public class MyAsyncHandler : IHttpAsyncHandler
{
    public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
    {
        // NOTE: the result of this operation is void, but TCS requires some data type so we just use bool
        TaskCompletionSource<bool> webClientDownloadCompletionSource = new TaskCompletionSource<bool>();

        WebClient webClient = new WebClient())
        HttpContext currentHttpContext = HttpContext.Current;

        // Setup the download completed event handler
        client.DownloadDataCompleted += (o, e) =>
        {
            if(e.Cancelled)
            {
                // If it was canceled, signal the TCS is cacnceled
                // NOTE: probably don't need this since you have nothing canceling the operation anyway
                webClientDownloadCompletionSource.SetCanceled();
            }
            else if(e.Error != null)
            {
                // If there was an exception, signal the TCS with the exception
                webClientDownloadCompletionSource.SetException(e.Error);
            }
            else
            {
                // Success, write the response
                currentHttpContext.Response.ContentType = "text/xml";
                currentHttpContext.Response.OutputStream.Write(e.Result, 0, e.Result.Length);

                // Signal the TCS that were done (we don't actually look at the bool result, but it's needed)
                taskCompletionSource.SetResult(true);
            }
        };

        string url = "url_web_service_url";

        // Kick off the download immediately
        client.DownloadDataAsync(new Uri(url));

        // Get the TCS's task so that we can append some continuations
        Task webClientDownloadTask = webClientDownloadCompletionSource.Task;

        // Always dispose of the client once the work is completed
        webClientDownloadTask.ContinueWith(
            _ =>
            {
                client.Dispose();
            },
            TaskContinuationOptions.ExecuteSynchronously);

        // If there was a callback passed in, we need to invoke it after the download work has completed
        if(cb != null)
        {
            webClientDownloadTask.ContinueWith(
               webClientDownloadAntecedent =>
               {
                   cb(webClientDownloadAntecedent);
               },
               TaskContinuationOptions.ExecuteSynchronously);
         }

        // Return the TCS's Task as the IAsyncResult
        return webClientDownloadTask;
    }

    public void EndProcessRequest(IAsyncResult result)
    {
        // Unwrap the task and wait on it which will propagate any exceptions that might have occurred
        ((Task)result).Wait();
    }

    public bool IsReusable
    {
        get 
        { 
            return true; // why not return true here? you have no state, it's easily reusable!
        }
    }

    public void ProcessRequest(HttpContext context)
    {
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文