[英]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.