[英]What is the best way to wait on a network packet using C#'s new async feature
我最近一直在玩新的Async CTP,我遇到過一種我不確定如何繼續的情況。
在我目前的代碼庫中,我使用的是“作業”和“作業管理器”的概念。 作業僅用於處理初始消息,發送響應,然后等待響應。
我已經有了基於同步套接字的現有代碼,其中網絡線程正在等待數據到達,然后將其傳遞給事件處理程序,最終傳遞給作業管理器。
作業管理器查找將處理消息的作業,並將其傳遞。
所以場景是這樣的:
這是一個偽代碼示例:
class MyJob : Job
{
public override void RunJob( IPacketMsg packet )
{
// handle packet
var myReply = new Packet();
SendReply( myReply );
await GetResponse();
}
}
但我不完全確定如何在第3步繼續。工作經理將獲得響應,然后將其交給正在運行的工作。 但我不知道如何讓工作等待回應。
我考慮過創建一個等待在WaitHandle上阻塞的任務,但這是最好的解決方案嗎?
在這種情況下我還能做些什么嗎?
編輯關於Async CTP的主題,在未使用UI的情況下會發生什么。 我已經閱讀了Eric Lippert的Async博客,但我不相信它曾經觸及過如何在沒有UI線程的情況下在后台工作的主題(它是關閉背景工作者還是......?)
- 工作經理獲得新消息並啟動工作。
- 作業啟動,處理消息並發送回復消息。
- 此時,作業將等待對回復的響應。
首先,我應該提到Async CTP非常好地處理異步操作 ,但異步事件並沒有那么多。 您可能想要考慮基於Rx的方法。 但是讓我們繼續使用Async CTP。
您有兩個基本選項來創建任務:
Task.Factory.StartNew
將在線程池上運行委托。 自定義任務工廠和調度程序為任務委托提供了更多選項(例如,指定委托必須在STA線程上運行)。 TaskFactory.FromAsync
包裝現有的Begin
/ End
方法對, TaskEx.FromResult
返回“future constant”, TaskCompletionSource
可用於顯式控制Task
( FromAsync
和FromResult
都在內部使用TCS
)。 如果作業處理受CPU約束,則將其傳遞給Task.Factory.StartNew
。 我將假設作業處理受CPU限制。
工作管理員偽代碼:
// Responds to a new message by starting a new job on the thread pool.
private void RespondToNewMessage(IPacketMsg message)
{
IJob job = ..;
Task.Factory.StartNew(job.RunJob(message));
}
// Holds tasks waiting for a response.
private ConcurrentDictionary<int, TaskCompletionSource<IResponse>> responseTasks = ..;
// Asynchronously gets a response for the specified reply.
public Task<IResponse> GetResponseForReplyAsync(int replyId)
{
var tcs = new TaskCompletionSource<IResponse>();
responseTasks.Add(replyId, tcs);
return tcs.Task;
}
// Responds to a new response by completing and removing its task.
private void RespondToResponse(IResponse response)
{
var tcs = responseTasks[response.ReplyId];
responseTasks.Remove(response.ReplyId);
tcs.TrySetComplete(response);
}
這個想法是,工作經理還管理一系列優秀的回復。 為了實現這一點,我引入了一個簡單的int
reply標識符,作業管理器可以使用該標識符來確定響應哪個響應。
現在工作可以像這樣工作:
public override void RunJob(IPacketMsg packet)
{
// handle packet
var myReply = new Packet();
var response = jobManager.GetResponseForReplyAsync(myReply.ReplyId);
SendReply(myReply);
await response;
}
因為我們將作業放在線程池線程上,所以有一些棘手的事情:
GetResponseForReplyAsync
(注冊任務),然后稍后await
。 這是為了避免在我們有機會注冊之前發送回復和收到回復的情況。 RespondToResponse
將在完成任務之前刪除任務注冊,以防萬一完成任務導致另一個回復以相同的id發送。 如果作業足夠短,不需要將它們放在線程池線程上,那么可以簡化解決方案。
關於Async CTP的主題,在沒有使用UI的情況下會發生什么。 我已經閱讀了Eric Lippert的Async博客,但我不相信它曾經觸及過如何在沒有UI線程的情況下在后台工作的主題(它是關閉背景工作者還是......?)
await
將返回其執行上下文。 在UI過程中,這是一個UI消息循環。 在ASP.NET中,這是ASP.NET線程池。 在其他情況下(控制台應用程序和Win32服務),沒有上下文,因此continuation排隊到ThreadPool
。 這通常不是所希望的行為,因此我編寫了一個可以在這些情況下使用的AsyncContext
類。
不使用BackgroundWorker
。 在像你這樣的服務器端場景中,根本沒有后台線程並不罕見。
您只需使用await模式連接事件處理程序的其余部分,如下所示:
public async void RunJob(IPacketMsg msg)
{
// Do Stuff
var response = await GetResponse();
// response is "string", not "Task<string>"
// Do More Stuff
}
public Task<string> GetResponse()
{
return Task.Factory.StartNew(() =>
{
_networkThingy.WaitForDataAvailable();
return _networkThingy.ResponseString;
});
}
當get響應任務完成時,方法的其余部分將在當前同步上下文中執行。 但是,在此之前,您的方法執行已經產生(因此,在GetResponse中啟動的任務完成之前,等待之后的任何代碼都不會運行)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.