2013-02-27 259 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