【问题标题】:Using an IHttpAsyncHandler to call a WebService Asynchronously使用 IHttpAsyncHandler 异步调用 WebService
【发布时间】:2011-06-18 01:15:47
【问题描述】:

这是基本设置。我们有一个 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,或者其他方法。欢迎任何反馈。谢谢!!

【问题讨论】:

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


    【解决方案1】:

    哇,是的,如果您使用的是 .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)
        {
        }
    }
    

    【讨论】:

    • 感谢您的帖子,我刚刚学到了很多关于 TPL 的知识。我更喜欢这个版本,谢谢!
    • TPL 是可扩展性怪胎最好的朋友。 ;) 只有当 .NET vNext 推出异步语言扩展时才会更好,而且您不需要自己编写所有这些疯狂的闭包和延续。编码愉快!
    • @DrewMarsh :我认为用于异步操作的线程是来自 asp.net 线程池的线程。我想改用后台线程(Thread.Start)。如何使用 Tasks 或 async/await 来实现?
    • 不是。 DownloadDataAaync 使用 I/O 线程,然后在同步上下文线程上触发事件。这是因为 WebClient 最初是为在 WinForms 中使用而设计的,并且希望简化将事件编组返回给调用者的工作。您可以做的另一件事是使用 HttpWebRequest 并使用其 async BeginGetResponse 并自己在 .NET 4.0 中从流中进行异步读取。这种方法永远不会再次使用同步上下文。要使用 await,您需要 .NET 4.5 API,甚至可以使用新的 WCF Web API 的 HttpClient 类。
    • 我写了这个示例hereasync 版本。 @Drew:ASP.NET 中的 SyncContext 是请求上下文;它没有特定的线程 (see my MSDN article) - 因此 WinForms 中的 WebClient 将其事件编组到 UI,但 ASP.NET 中的 WebClient 将其事件编组到请求上下文。它不是特定于 UI 的类型。
    猜你喜欢
    • 2017-04-01
    • 1970-01-01
    • 2018-01-23
    • 1970-01-01
    • 1970-01-01
    • 2016-11-07
    • 1970-01-01
    • 1970-01-01
    • 2013-08-07
    相关资源
    最近更新 更多