Ric*_*ook 5 c# network-programming task-parallel-library async-await windows-runtime
我想与TPL包裹以下数据报套接字操作清理API,使其与工作很好async和await,很像StreamSocket类一样.
public static async Task<bool> TestAsync(HostName hostName, string serviceName, byte[] data)
{
var tcs = new TaskCompletionSource<bool>();
var socket = new DatagramSocket();
socket.MessageReceived += (sender, e) =>
{
var status = false; // Status value somehow derived from e etc.
tcs.SetResult(status);
};
await socket.ConnectAsync(hostName, serviceName);
var stream = await socket.GetOutputStreamAsync();
var writer = new DataWriter(stream);
writer.WriteBytes(data);
await writer.StoreAsync();
return tcs.Task;
}
Run Code Online (Sandbox Code Playgroud)
关键点是MessageReceived将DatagramSocket类转换为事件异步模式和新async模式的奇怪混乱的事件.无论如何,TaskCompletionSource<T>允许我调整处理程序以符合后者,所以这不是太可怕.
除非端点永远不返回任何数据,否则这似乎工作得很好.与MessageReceived处理程序关联的任务永远不会完成,因此返回的任务TestAsync永远不会完成.
什么是正确的包装此操作以包含超时和取消的正确方法?我想扩展这个函数来CancellationToken为后者做一个论证,但是我该怎么做呢?我唯一想到的就是创建一个额外的"监控"任务,使用Task.Delay该任务我传递超时值和取消令牌以支持以下两行中的这两种行为:
public static async Task<bool> CancellableTimeoutableTestAsync(HostName hostName, string serviceName, byte[] data, CancellationToken userToken, int timeout)
{
var tcs = new TaskCompletionSource<bool>();
var socket = new DatagramSocket();
socket.MessageReceived += (sender, e) =>
{
var status = false; // Status value somehow derived from e etc.
tcs.SetResult(status);
};
await socket.ConnectAsync(hostName, serviceName);
var stream = await socket.GetOutputStreamAsync();
var writer = new DataWriter(stream);
writer.WriteBytes(data);
await writer.StoreAsync();
var delayTask = Task.Delay(timeout, userToken);
var t1 = delayTask.ContinueWith(t => { /* Do something to tcs to indicate timeout */ }, TaskContinuationOptions.OnlyOnRanToCompletion);
var t2 = delayTask.ContinueWith(t => { tcs.SetCanceled(); }, TaskContinuationOptions.OnlyOnCanceled);
return tcs.Task;
}
Run Code Online (Sandbox Code Playgroud)
然而,这具有各种问题,包括延迟任务和MessageReceived处理程序之间的潜在竞争条件.我从来没有能够使这种方法可靠地工作,而且它看起来非常复杂以及线程池的低效使用.它很狡猾,容易出错并且伤到我的脑袋.
旁注:我是唯一一个对DatagramSocketAPI 感到困惑的人吗?它不仅似乎是IAsyncActionWinRT模型和TPL 的丑陋集合,还有一些棘手的EAP,我对用于表示基本无连接协议(例如包含其中命名的UDP的UDP)的API并不十分满意ConnectAsync.这对我来说似乎是一个矛盾.
DatagramSocket首先,我认为正是由于 UDP 的性质,接口才有意义。如果您有数据报流,那么事件是表示它的合适方法。WinRT IAsyncAction(或.Net Task)只能表示拉模型,您在其中显式请求每条数据(例如,可能有一个方法ReadNextDatagramAsync())。这对于 TCP 来说是有意义的,因为它具有流量控制,因此如果您缓慢地读取数据,发送方也会缓慢地发送它们。但对于 UDP,推送模型(由 WinRT 和 .Net 中的事件表示)更有意义。
我同意这个名字Connect并不是 100% 有意义,但我认为它基本上是有意义的,特别是让它与StreamSocket. 而你确实需要这样的方法,以便系统可以解析域名并为你的套接字分配端口。
对于你的方法,我同意@usr的观点,你应该创建一个单独的方法来接收数据报。如果您想将一个异步模型转换为另一个异步模型,同时添加原始模型本身不支持的功能,那么这会很麻烦,我认为您对此无能为力。
如果您正确实现它,它也不会效率低下:您应该确保在完成后Task,MessageReceived事件被取消订阅,与关联的计时器Delay()被释放(您可以通过取消传递给的令牌来做到这一点Delay())和委托通过传入注册的内容CancellationToken未注册(我认为您应该Register()直接使用而不是 (ab)using Delay())。
关于竞争条件,当然你必须考虑它们。但这里有一个相对简单的方法来处理这个问题:使用(eg )Try的方法。TaskCompletionSourceTrySetResult()