Использование IHttpAsyncHandler для асинхронного вызова WebService - PullRequest
6 голосов
/ 17 июня 2011

Вот основные настройки. У нас есть приложение ASP.Net WebForms со страницей, на которой есть приложение Flash, которому необходим доступ к внешней веб-службе. Из-за (я полагаю, безопасности) ограничений во Flash (не спрашивайте меня, я вообще не эксперт по Flash), мы не можем подключиться к веб-службе напрямую из Flash. Обходной путь заключается в создании прокси в ASP.Net, который будет вызывать приложение Flash, которое, в свою очередь, вызовет WebService и перенаправит результаты обратно в приложение Flash.

На веб-сайте очень высокий трафик, и проблема в том, что если веб-служба вообще зависнет, потоки запросов ASP.Net начнут резервное копирование, что может привести к серьезному истощению потоков. Чтобы обойти это, я решил использовать IHttpAsyncHandler , который был разработан именно для этой цели. В нем я буду использовать WebClient для асинхронного вызова веб-службы и пересылки ответа обратно. В сети очень мало примеров того, как правильно использовать 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 кажется излишним, мне просто интересно, есть ли способ использовать IAsyncResult, возвращенный из Delegate.BeginInvoke, или, может быть, что-то еще. Любые отзывы приветствуются. Спасибо !!

1 Ответ

8 голосов
/ 17 июня 2011

Ничего себе, да, вы можете сделать это намного проще / чище, если вы используете .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)
    {
    }
}
...