C# 如何调整DatagramSocket.MessageReceived以与async/await一起使用?

C# 如何调整DatagramSocket.MessageReceived以与async/await一起使用?,c#,network-programming,windows-runtime,task-parallel-library,async-await,C#,Network Programming,Windows Runtime,Task Parallel Library,Async Await,我想用TPL包装下面的数据报套接字操作,以清理API,使其能够与async和wait很好地工作,就像StreamSocket类一样 public static async Task<bool> TestAsync(HostName hostName, string serviceName, byte[] data) { var tcs = new TaskCompletionSource<bool>(); var socket = new Datagram

我想用TPL包装下面的数据报套接字操作,以清理API,使其能够与
async
wait
很好地工作,就像
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
API弄糊涂的人吗?它看起来不仅是
iasyncation
WinRT模型和TPL的丑陋组合,还加入了一些棘手的EAP,我对一个旨在表示基本无连接协议的API不太满意,比如其中包含名为
ConnectAsync
的UDP方法。这对我来说似乎是一个矛盾。

超时:启动计时器并使用
tcs.TrySetCancelled()
完成任务。对于取消,请使用
cancellationToken.Register
注册一个回调,在该回调中您也设置为已取消。务必小心处理计时器


我建议您将计时器逻辑移到一个可重用的助手方法中。这就避免了代码看起来像意大利面条一样,夹杂着许多不相关的东西。

首先,我认为
DatagramSocket
的接口之所以有意义,正是因为UDP的本质。如果您有一个数据报流,那么事件是表示该流的适当方式。WinRT
iSyncAction
(或.Net
Task
)只能表示pull模型,在该模型中,您可以显式请求每段数据(例如,可能有一个方法
ReadNextDatagramAsync()
)。这对于TCP来说是有意义的,因为它有流量控制,所以如果您缓慢地读取数据,发送方也会缓慢地发送数据。但是对于UDP,推送模型(由WinRT和.Net中的事件表示)更有意义

我同意名称
Connect
没有100%的意义,但我认为它大部分是有意义的,特别是为了使它与
StreamSocket
更一致。您确实需要这样的方法,以便系统能够解析域名并为您的套接字分配端口

对于您的方法,我同意@usr,您应该创建一个单独的方法来接收数据报。如果您想将一个异步模型转换为另一个,同时添加原始模型本机不支持的功能,那么它将非常灵活,我认为您对此无能为力

如果您正确地实现它,它也不会低效:您应该确保在
任务
完成后,
MessageReceived
事件被取消订阅,与
Delay()
相关联的计时器被释放(您可以通过取消传递给
Delay()
的令牌来实现这一点)并且使用传入的
CancellationToken
注册的委托是未注册的(我认为您应该直接使用,而不是使用
Delay()


关于比赛条件,当然你必须考虑它们。但是这里有一个相对简单的方法来解决这个问题:使用
TaskCompletionSource
Try
方法(例如)。

而不是整个第二个方法,创建一个新方法,将第一个方法中的task与task.Delay和uses task.WhenAny相结合。返回后,您要么完成任务,要么超时。
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;
}