使用IHttpAsyncHandler异步调用WebService

时间:2011-06-17 16:28:28

标签: c# .net asp.net asynchronous ihttpasynchandler

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

WebSite的流量非常高,问题是,如果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)
    {
    }
}

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

1 个答案:

答案 0 :(得分:8)

哇,是的,如果你通过利用任务并行库来使用.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)
    {
    }
}