public IAsyncResult BeginProcessRequest(HttpContextBase context, AsyncCallback callback, object state)
        {
            var tcs = new TaskCompletionSource<Action>(state);
            if (callback != null)
                tcs.Task.ContinueWith(task => callback(task), TaskContinuationOptions.ExecuteSynchronously);

            var request = context.Request;
            var response = context.Response;

            var pathBase = request.ApplicationPath;
            if (pathBase == "/" || pathBase == null)
                pathBase = "";

            if (_root != null)
                pathBase += _root;

            var path = request.Path;
            if (path.StartsWith(pathBase))
                path = path.Substring(pathBase.Length);

            var serverVarsToAddToEnv = request.ServerVariables.AllKeys
                .Where(key => !key.StartsWith("HTTP_") && !string.Equals(key, "ALL_HTTP") && !string.Equals(key, "ALL_RAW"))
                .Select(key => new KeyValuePair<string, object>(key, request.ServerVariables.Get(key)));

            var env = new Dictionary<string, object>();
            env[OwinConstants.Version] = "1.0";
            env[OwinConstants.RequestMethod] = request.HttpMethod;
            env[OwinConstants.RequestScheme] = request.Url.Scheme;
            env[OwinConstants.RequestPathBase] = pathBase;
            env[OwinConstants.RequestPath] = path;
            env[OwinConstants.RequestQueryString] = request.ServerVariables["QUERY_STRING"];
            env[OwinConstants.RequestProtocol] = request.ServerVariables["SERVER_PROTOCOL"];
            env["aspnet.HttpContextBase"] = context;
            env[OwinConstants.CallCompleted] = tcs.Task;

#if ASPNET_WEBSOCKETS
            if (context.IsWebSocketRequest)
                env[OwinConstants.WebSocketSupport] = "WebSocketFunc";
#endif

            foreach (var kv in serverVarsToAddToEnv)
                env["server." + kv.Key] = kv.Value;

            var requestHeaders = request.Headers.AllKeys
                    .ToDictionary(x => x, x => request.Headers.GetValues(x), StringComparer.OrdinalIgnoreCase);

            var requestStream = request.InputStream;

            try
            {
                _app.Invoke(env, requestHeaders, requestStream)
                    .ContinueWith(taskResultParameters =>
                    {
                        if (taskResultParameters.IsFaulted)
                        {
                            tcs.TrySetException(taskResultParameters.Exception.InnerExceptions);
                        }
                        else if (taskResultParameters.IsCanceled)
                        {
                            tcs.TrySetCanceled();
                        }
                        else
                        {
                            try
                            {
                                var resultParameters = taskResultParameters.Result;
                                var properties = resultParameters.Item1;
                                var responseStatus = resultParameters.Item2;
                                var responseHeader = resultParameters.Item3;
                                var responseCopyTo = resultParameters.Item4;

                                response.BufferOutput = false;
                                response.StatusCode = responseStatus;

                                object reasonPhrase;
                                if (properties.TryGetValue(OwinConstants.ReasonPhrase, out reasonPhrase))
                                    response.StatusDescription = Convert.ToString(reasonPhrase);

                                if (responseHeader != null)
                                {
                                    foreach (var header in responseHeader)
                                    {
                                        foreach (var headerValue in header.Value)
                                            response.AddHeader(header.Key, headerValue);
                                    }
                                }

#if ASPNET_WEBSOCKETS
                                object tempWsBodyDelegate;
                                if (responseStatus == 101 &&
                                    properties.TryGetValue(OwinConstants.WebSocketBodyDelegte, out tempWsBodyDelegate) &&
                                    tempWsBodyDelegate != null)
                                {
                                    var wsBodyDelegate = (WebSocketAction)tempWsBodyDelegate;
                                    context.AcceptWebSocketRequest(async websocketContext =>
                                    {
                                        env["aspnet.AspNetWebSocketContext"] = websocketContext;
                                        var webSocket = websocketContext.WebSocket;

                                        await wsBodyDelegate(WebSocketSendAsync(webSocket), WebSocketReceiveAsync(webSocket), WebSocketCloseAsync(webSocket));

                                        switch (webSocket.State)
                                        {
                                            case WebSocketState.Closed:  // closed gracefully, no action needed
                                            case WebSocketState.Aborted: // closed abortively, no action needed
                                                break;
                                            case WebSocketState.CloseReceived:
                                                await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
                                                break;
                                            case WebSocketState.Open:
                                            case WebSocketState.CloseSent: // No close received, abort so we don't have to drain the pipe.
                                                websocketContext.WebSocket.Abort();
                                                break;
                                            default:
                                                throw new ArgumentOutOfRangeException("state", webSocket.State, string.Empty);
                                        }

                                        if (responseCopyTo != null)
                                            await responseCopyTo(response.OutputStream);

                                        response.Close();
                                    });

                                    tcs.TrySetResult(() => { });
                                }
                                else
#endif
                                    if (responseCopyTo != null)
                                    {
                                        responseCopyTo(response.OutputStream)
                                            .ContinueWith(taskCopyTo =>
                                            {
                                                if (taskResultParameters.IsFaulted)
                                                    tcs.TrySetException(taskResultParameters.Exception.InnerExceptions);
                                                else if (taskResultParameters.IsCanceled)
                                                    tcs.TrySetCanceled();
                                                else
                                                    tcs.TrySetResult(() => { });
                                            });
                                    }
                                    else
                                    {
                                        // if you reach here it means you didn't implmement AppAction correctly
                                        // tcs.TrySetResult(() => { });
                                    }
                            }
                            catch (Exception ex)
                            {
                                tcs.TrySetException(ex);
                            }
                        }
                    });
            }
            catch (Exception ex)
            {
                tcs.TrySetException(ex);
            }

            return tcs.Task;
        }