user1084447
user1084447

Reputation: 899

Return before async Task complete

I'm working on an ASP.NET MVC 4 web application. I'm using .NET 4.5 and am trying to take advantage of the new asynchronous API's.

I have a couple situations where I want to schedule an async Task to run later while I return back an immediately important value right away. For example, here is a "Login" method which I want to return a new SessionID as quickly as possible, but once I've returned the SessionID I want to clean out old expired SessionID's:

public async Task<Guid> LogIn(string UserName, string Password)
{
    //Asynchronously get ClientID from DB using UserName and Password

    Session NewSession = new Session()
    {
        ClientID = ClientID,
        TimeStamp = DateTime.Now
    };
    DB.Sessions.Add(NewSession);
    await DB.SaveChangesAsync();    //NewSession.ID is autopopulated by DB

    CleanSessions(ClientID);    //Async method which I want to execute later

    return NewSession.ID;
}

private async void CleanSessions(int ClientID)
{
    //Asynchronously get expired sessions from DB based on ClientID and mark them for removal
    await DB.SaveChangesAsync();
}

I've tried a bunch of different things including combinations of Task.Run() and Parallel.Invoke() but CleanSessions never gets called. How do I achieve background task scheduling?

Upvotes: 10

Views: 14077

Answers (2)

Marc Gravell
Marc Gravell

Reputation: 1064204

If, as has been noted, you are trying to do something "fire and forget", but inside ASP.NET - then you might want to look at ConfigureAwait; you can use this as part of the await to tell it to ignore the sync-context. It is the sync-context that ties it back into the ASP.NET execution pipeline, but if that isn't necessary you can avoid it. For example:

public async Task<Guid> LogIn(string UserName, string Password)
{
    //Asynchronously get ClientID from DB using UserName and Password

    Session NewSession = new Session()
    {
        ClientID = ClientID,
        TimeStamp = DateTime.Now
    };
    DB.Sessions.Add(NewSession);
    await DB.SaveChangesAsync().ConfigureAwait(false);    //NewSession.ID is autopopulated by DB

    await CleanSessions(ClientID).ConfigureAwait(false);    //Async method which I want to execute later

    return NewSession.ID;
}
// simplified: no need for this to be full "async"
private Task CleanSessions(int ClientID)
{
    //Asynchronously get expired sessions from DB based on ClientID
    return DB.SaveChangesAsync();
}

Upvotes: 0

Stephen Cleary
Stephen Cleary

Reputation: 457402

Running tasks in ASP.NET without a request is not recommended. It's dangerous.

That said, change CleanSessions to return Task and you can do it like this:

Task.Run(() => CleanSessions());

In your case, I think it would be OK, because there's no long-term problem if CleanSessions doesn't execute or gets terminated in the middle of executing (which can happen in ASP.NET due to recycling). If you want to notify ASP.NET that you have some work in progress that is not associated with a request, you can use the BackgroundTaskManager from my blog like this:

BackgroundTaskManager.Run(() => CleanSessions());

Upvotes: 18

Related Questions