ASP.NET: Is it possible to call async Task in Glob

2019-03-12 18:15发布

问题:

I need to call some async operations on my DB in Global.asax. for example in Application_AuthenticateRequest I need to Authenticate user against DB Is it possible with async Tasks?

回答1:

There's an easier way to do this now:

    public MvcApplication()
    {
        var wrapper = new EventHandlerTaskAsyncHelper(DoAsyncWork);
        this.AddOnAuthenticateRequestAsync(wrapper.BeginEventHandler, wrapper.EndEventHandler);
    }

    private async Task DoAsyncWork(object sender, EventArgs e)
    {
        var app = (HttpApplication)sender;
        var ctx = app.Context;

        ...
        await doSomethingAsync();
    }

With this approach, you define a method using the async keyword and wrap that method using the 'EventHandlerTaskAsyncHelper' class to generate BeginEventHandler and EndEventHandler methods to pass into the AddOnAuthenticateRequestAsync call.



回答2:

I didn't find a way to using the new C# keyword async and await, but we can still using APM pattern to use async operations in Global.asax because it implemented IHttpAsyncHandler interface. Here is a small code to demo async, here I use a WebRequst as example, in your case please use database operation instead.

    public Global()
    {
        this.AddOnAuthenticateRequestAsync(BeginGetAsyncData, EndGetAsyncData);
    }

    IAsyncResult BeginGetAsyncData(Object src, EventArgs args, AsyncCallback cb, Object state)
    {
        Console.WriteLine("BeginGetAsyncData: thread #" + System.Threading.Thread.CurrentThread.ManagedThreadId);
        WebRequest request = WebRequest.Create("http://www.google.com");
        return request.BeginGetResponse(cb, request); // call database async operation like SqlCommand.BeginExecuteReader()
    }

    void EndGetAsyncData(IAsyncResult ar)
    {
        Console.WriteLine("EndGetAsyncData: thread #" + System.Threading.Thread.CurrentThread.ManagedThreadId);

        WebRequest requst = (WebRequest)ar.AsyncState;
        System.Net.WebResponse response = requst.EndGetResponse(ar); // call database async operation like SqlCommand.EndExecuteReader()

        Console.WriteLine(new StreamReader(response.GetResponseStream()).ReadToEnd());
        response.Close();
    }


回答3:

You have to add the async version of the AuthenticateRequest yourself. Using the following code:

public MvcApplication()
{
    // Contrary to popular belief, this is called multiple times, one for each 'pipeline' created to handle a request.
    // Wire up the async authenticate request handler.
    AddOnAuthenticateRequestAsync(BeginAuthenticateRequest, EndAuthenticateRequest, null);
}

The issue is then, how to implement BeginAuthenticateRequest and EndAuthenticateRequest using the new async/await features of C#. First, let's get our async version of AuthenticateRequest out of the way:

private async Task AuthenticateRequestAsync(object sender, EventArgs args)
{
    // Yay, let's do async stuff!
    await ...
}

What we need to do next is come up with an implementation of BeginAuthenticateRequest and EndAuthenticateRequest. I followed a blog post, but derived my own implementation:

private IAsyncResult BeginAuthenticateRequest(object sender, EventArgs args, AsyncCallback callback, object state)
{
    Task task = AuthenticateRequestAsync(sender, args);
    var tcs = new TaskCompletionSource<bool>(state);

    task.ContinueWith(_ =>
    {
        if (task.IsFaulted && task.Exception != null) tcs.TrySetException(task.Exception.InnerExceptions);
        else if (task.IsCanceled) tcs.TrySetCanceled();
        else tcs.TrySetResult(true);

        if (callback != null) callback(tcs.Task);
    }, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.Default);

    return tcs.Task;
}

You can read the entire linked article to see how it works, but basically IAsyncResult is implemented by Task, so all you have to do is call the callback when done.

The last bit is dead easy:

private void EndAuthenticateRequest(IAsyncResult result)
{
    // Nothing to do here.   
}