假設你正在試圖並行的若干由控制器操作調用異步任務,並假設你只想要一個(明確的)任務完成後返回一個響應返回給客戶端,無需等待所有響應,(發射後不管)你可以簡單地調用異步方法無需等待着他們:
// Random async method here ...
private async Task<int> DelayAsync(int seconds)
{
await Task.Delay(seconds*1000)
.ConfigureAwait(false);
Trace.WriteLine($"Done waiting {seconds} seconds");
return seconds;
}
[HttpGet]
public async Task<IHttpActionResult> ParallelBackgroundTasks()
{
var firstResult = await DelayAsync(6)
.ConfigureAwait(false);
// Initiate unawaited background tasks ...
#pragma warning disable 4014
// Calls will return immediately
DelayAsync(100);
DelayAsync(111);
// ...
#pragma warning enable 4014
// Return first result to client without waiting for the background task to complete
return Ok(firstResult);
}
如果在完成所有後臺任務後,即使原始請求線程已完成,仍需要進一步處理,但仍然是po ssible完成後安排的延續:
#pragma warning disable 4014
var backgroundTasks = Enumerable.Range(1, 5)
.Select(DelayAsync);
// Not awaited
Task.WhenAll(backgroundTasks)
.ContinueWith(t =>
{
if (t.IsFaulted)
{
// Exception handler here
}
Trace.WriteLine($"Done waiting for a total of {t.Result.Sum()} seconds");
});
#pragma warning restore 4014
更好的仍然是重構後臺工作納入自己的異步方法,其中的異常處理的好處是可以:
private async Task ScheduleBackGroundWork()
{
try
{
// Initiate unawaited background tasks
var backgroundTasks = Enumerable.Range(1, 5)
.Select(DelayAsync);
var allCompleteTask = await Task.WhenAll(backgroundTasks)
.ConfigureAwait(false);
Trace.WriteLine($"Done waiting for a total of {allCompleteTask.Sum()} seconds");
}
catch (Exception)
{
Trace.WriteLine("Oops");
}
}
的調用後臺工作仍然是unawaited,即:
#pragma warning disable 4014
ScheduleBackGroundWork();
#pragma warning restore 4014
注意
假設有不CPU綁定完成工作之前的最內AWAIT,這種方法具有超過在於它使用較少的線程池線程使用Task.Run()
的優點。儘管任務是在控制器的線程池線程上串行創建的,但是當IO界限工作完成時,延續(Trace.WriteLine
)每個都需要一個線程來完成,因此,如果所有的延續都是同時完成的話,這仍然會導致飢餓 - 您不會希望多個客戶端調用這些類型的函數,這是出於可伸縮性原因。
顯然,客戶端實際上並不知道所有任務的最終結果結果是什麼,所以您可能需要添加額外的狀態以在實際工作完成後通知客戶端(例如,通過SignalR)。此外,如果應用程序池死亡或被回收,結果將會丟失。
您還可以得到一個編譯器警告,當你不等待異步方法的結果 - 這可以通過編譯被抑制。
當使用未等待的任務時,您也可能想在不等待的情況下調用異步代碼時放入全局未觀察任務異常處理程序。更多關於此here
編輯 - 再擴展性
說實話,這將取決於很多你打算用「背景」的任務做什麼。考慮這個更新的「後臺任務」:
private async Task<int> DelayAsync(int seconds)
{
// Case 1 : If there's a lot of CPU bound work BEFORE the innermost await:
Thread.Sleep(1000);
await Task.Delay(seconds*1000)
.ConfigureAwait(false);
// Case 2 : There's long duration CPU bound work in the continuation task
Thread.Sleep(1000);
Trace.WriteLine($"Done waiting {seconds} seconds");
return seconds;
}
- 如果你需要做的CPU密集型工作之前打到最裏面
await
(案例1,以上),你會需要求助於 喬納森的Task.Run()
策略去耦等待客戶 從「外殼1的工作訪問控制器(否則客戶端將被迫等待)。這樣做會咀嚼每項任務約1個線程。
- 類似地,在情況2中,如果在執行
await
, 之後執行CPU密集型工作,則在剩餘工作的持續時間內計劃繼續計劃將消耗 線程。雖然這不會影響原始客戶端調用持續時間,但會影響整個進程線程和CPU使用率。
- 但是,如果你的後臺任務很少做其他比 卸載工作的一些外部IO勢必活動(例如數據庫,外部Web服務等),很少或根本沒有前置和後置IO處理,然後將任務的剩餘將會很快完成,線程的使用將會微不足道。
- 對於後臺的持續時間等待IO綁定操作,應該是完全沒有線消耗量(見明確There is no Thread)
所以我想答案是「看情況」。你也許可以矇混過關,沒有前+後處理上自託管Owin服務幾unawaited的任務,但如果你使用Azure的,然後像Azure Web Jobs聽起來像後臺處理一個更好的選擇。
我不認爲這是去工作。服務器只允許來自每個客戶端的一個連接。這樣做是爲了防止黑客在網頁上發起服務攻擊。因此,同時對多個操作執行異步操作會將服務器視爲服務攻擊。 – jdweng
這不正是發生了什麼,對吧?我的意思是,一個功能是私人的,我只想在客戶端斷開後處理任務 –
如果您有Resharper或配置了「StyleCop」,您將收到有關「代碼無法到達的通知」。 – Fabio