[英]async await for a single task at a time
在我的求職面試中,我接到了一項任務,即在一些長時間運行的方法上創建一個異步包裝器,處理一些數據,但要創建它以便一次只能運行一個任務。 我對async/await
模式不是很熟悉,所以我盡力了,寫了一些 task-style 和 event-style 之間的混合,這樣我的包裝器就持有一個當前正在執行的任務,並暴露一個公共方法和一個公共事件. 方法以要處理的數據為參數,如果沒有正在運行的任務,則啟動一個,如果有任務,則將數據入隊。 任務在完成后引發公共事件,該事件將過程結果發送給訂閱者,並在有任何排隊的情況下啟動新任務。
所以,到那時你可能已經猜到了,我面試失敗了,但現在我做了一些研究,我試圖弄清楚如何正確地做到這一點(它也應該是線程安全的,但我太忙了擔心那個)。 所以我的問題是,如果我有
public class SynchronousProcessor
{
public string Process(string arg)
{
Thread.Sleep(1500); //Work imitation
return someRandomString;
}
}
public class AsynchronousWrapper
{
SynchronousProcessor proc = new SynchronousProcessor();
public async Task<string> ProcessAsync(string arg)
{
return Task.Run(() => proc.Process(arg));
}
}
,或類似的東西,如果已經有任務在執行,我該如何正確處理對ProcessAsync(string)
調用?
許多求職面試問題的目的不是為了看你寫代碼。 通常,問題有點含糊,特別是看您提出的澄清問題 -您的問題決定了您的表現。 在白板上編寫代碼充其量是次要的。
我的任務是在一些長時間運行的方法上創建一個異步包裝器,處理一些數據
第一個問題:這個長時間運行的方法是異步的嗎? 如果是這樣,則不需要Task.Run
。 但如果不是...
后續問題:如果它不是異步的,應該是嗎? 即,它是基於 I/O 的嗎? 如果是這樣,那么我們可以投入時間使其正確異步。 但如果不是...
后續問題:如果我們需要一個任務包裝器(圍繞基於 CPU 的代碼或圍繞阻塞 I/O 代碼),環境是否適合包裝器? 即,這是一個桌面/移動應用程序,而不是將在 ASP.NET 中使用的代碼?
創建它以便一次只能運行一個任務。
澄清問題:如果第二個請求已經在運行,第二個請求是否“排隊”? 或者它會與現有請求“合並”嗎? 如果合並,他們是否需要“關閉”輸入數據 - 或輸入數據的某個子集?
這些問題中的每一個都會改變答案的結構。
公開一個公共方法和一個公共事件。
這可能是扔它的原因。 在Task<T>
/ IProgress<T>
和 Rx 之間,很少需要事件。 只有當你所在的團隊不會學習 Rx 時,才應該使用它們。
哦,不要擔心“失敗”面試。 在我的職業生涯中,我有超過 2/3 的面試“失敗”。 我只是不擅長面試。
正如@MickyD 已經說過的,您需要了解異步編程中的最佳實踐才能以正確的方式解決此類問題。 您的解決方案具有代碼異味,因為它為同步代碼提供了帶有Task.Run
異步包裝器。 當您被問及圖書館開發時,它將對您的圖書館消費者產生很大影響。
您必須了解asynchronous
不是multithreading
,因為它可以用一個線程完成。 這就像等待郵件一樣——你不會雇佣工人在郵箱旁等待。
這里的其他解決方案不是異步的,因為違反了async
代碼的其他規則:不要阻止 async action ,因此您應該避免使用lock
構造。
所以,回到你的問題:如果你面臨一項任務,其中規定
一次只能運行一個任務
這不是關於lock
( Monitor
),而是關於Semaphore(Slim)
。 如果將來出於某種原因您需要改進您的代碼以便可以同時執行多個任務,您將不得不重寫您的代碼。 在使用Semaphore
情況下,您只需要更改一個常量。 它還有一個用於等待方法的async
包裝器
所以你的代碼可以是這樣的(注意Task.Run
被刪除了,因為提供等待是客戶端的責任):
public class AsynchronousWrapper
{
private static SemaphoreSlim _mutex = new SemaphoreSlim(1);
public async Task<T> ProcessAsync<T>(Task<T> arg)
{
await _mutex.WaitAsync().ConfigureAwait(false);
try
{
return await arg;
}
finally
{
_mutex.Release();
}
}
}
這取決於您想要獲得的花哨程度。 一種簡單的方法是存儲一個任務,並將后續任務鏈接起來(有點同步):
public class AsynchronousWrapper
{
private Task previousTask = Task.CompletedTask;
private SynchronousProcessor proc = new SynchronousProcessor();
public Task<string> ProcessAsync(string arg)
{
lock (proc)
{
var task = previousTask.ContinueWith(_ => proc.Process(arg));
previousTask = task;
return task;
}
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.