【发布时间】:2013-02-08 20:54:21
【问题描述】:
我想用 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;
}
关键点是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;
}
但是,这会带来各种问题,包括延迟任务和MessageReceived 处理程序之间的潜在竞争条件。我从来没有能够让这种方法可靠地工作,而且它看起来非常复杂,而且线程池的使用效率低下。这很繁琐,容易出错,而且让我头疼。
旁注:我是唯一对DatagramSocket API 感到困惑的人吗?它不仅看起来像是IAsyncAction WinRT 模型和 TPL 的丑陋组合,并带有一些棘手的 EAP,而且我对旨在表示基本无连接协议(例如 UDP)的 API 不太满意,其中包含名为的方法ConnectAsync 在其中。这对我来说似乎是一个矛盾。
【问题讨论】:
-
创建一个新方法,将第一个任务与 Task.Delay 结合起来,并使用 Task.WhenAny,而不是整个第二个方法。一旦它返回,您要么完成任务,要么发生超时。
标签: c# network-programming windows-runtime task-parallel-library async-await