2013-02-27 263 views
6

我正在开发ASP.NET MVC 4 Web应用程序。我正在使用.NET 4.5,并试图利用新的异步API。异步任务完成之前返回

我有一些情况下,我想安排一个异步任务以后运行,而我立即返回一个立即重要的值。例如,这里有一个“登录”的方法,我想尽快返回一个新的SessionID,但一旦我返回的SessionID我想清理掉旧的过期的SessionID的:

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(); 
} 

我已经尝试了一堆不同的东西,包括Task.Run()和Parallel.Invoke()的组合,但CleanSessions永远不会被调用。我如何实现后台任务调度?

+0

基于此,当'SaveChangesAsync'完成时'CleanSessions'应该*启动*。它也许可以用一个'Task'和'await'来处理错误等 - 但是它应该按照我现在看到的那样“按现状”工作 – 2013-02-27 23:37:45

+0

注意:你应该从未使用异步无效的方法,顶级事件处理程序除外。 – 2013-02-27 23:39:18

+0

@MarcGravell:在发送响应之前,ASP.NET将等待所有'async void'方法的完成。操作者想要一个忘却火的人。 – 2013-02-27 23:40:36

回答

12

不建议在ASP.NET中运行没有请求的任务。 It's dangerous.

这就是说,改变CleanSessions返回Task,你可以做这样的:

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

在你的情况,我认为这将是美好的,因为没有长期的问题,如果CleanSessions没有按”在执行过程中执行或终止(这可能由于回收而在ASP.NET中发生)。如果你想通知你正在进行一些工作不与请求相关ASP.NET,您可以使用BackgroundTaskManager from my blog这样的:

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

这正是我一直在寻找的,谢谢。我实际上已经尝试过这个,但是我的CleanSessions()方法中有一个错误导致了异常,并且由于该方法在没有请求的情况下执行,所以异常未被捕获。这给我的印象是这个方法根本没有被调用! 因此,任何人都希望使用此技巧时,请务必阅读此答案中的链接,并确保您真正理解为什么这是“危险的”。 – user1084447 2013-02-28 22:19:42

+0

4年后呢?也许是更新?您的博客自2014年以来没有更新,有任何新的见解? – CularBytes 2018-01-21 11:00:42

+0

@CularBytes:No; ASP.NET仍然以相同的方式工作,这个答案仍然是完全有效的。 'BackgroundTaskManager'不能在ASP.NET Core上运行,但你可以使用'IHostedService'或者'IApplicationLifetime'来代替'IRegisteredObject'。 – 2018-01-22 14:45:10

0

如果,正如已经指出的,你正在试图做的一些“火和忘记”,但在ASP.NET内部 - 那么你可能想看看ConfigureAwait;你可以使用它作为await的一部分,告诉它忽略同步上下文。将同步上下文重新绑定到ASP.NET执行管道中,但如果没有必要,您可以避免它。例如:

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(); 
} 
+1

该解决方案具有最佳的错误处理(在CleanSessions中使用'await'),但不会返回“early”响应; “CleanSessions”完成后,响应将被发送。 – 2013-02-28 15:13:15

+1

嗯,是的 - 尽管*这个代码*已经使用了ConfigureAwait,那么在这些“等待”完成之前,显然不能发送回应。因此,如果您想象等待此任务的*调用*代码,它将位于ASP.NET同步上下文中。 – 2013-02-28 20:30:12

+0

是的,我给了这个尝试,它仍然在等待CleanSessions()继续之前。它没有完成我想要做的事情。 – user1084447 2013-02-28 22:11:31