Vorrei chiudere la seguente operazione di socket datagramma con TPL per ripulire l'API in modo che funzioni correttamente con async
e await
, proprio come fa la classe StreamSocket
.Come si adatta DatagramSocket.MessageReceived per l'uso con async/await?
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;
}
Il punto critico è l'evento MessageReceived
che trasforma la classe DatagramSocket
in uno strano miscuglio di modello asincrono evento e la nuova async
modello. Ad ogni modo, TaskCompletionSource<T>
mi consente di adattare il gestore per conformarsi a quest'ultimo, quindi non è troppo spaventoso.
Questo sembra funzionare piuttosto bene a meno che l'endpoint non restituisca mai alcun dato. L'attività associata al gestore MessageReceived
non viene mai completata e pertanto l'attività restituita da TestAsync
non viene mai completata.
Qual è il modo corretto di avvolgere questa operazione per incorporare un timeout e una cancellazione? Vorrei estendere questa funzione per prendere un argomento CancellationToken
per quest'ultimo, ma cosa devo fare con esso? L'unica cosa che mi è venuta in mente è quello di creare un compito aggiuntivo "monitoraggio" utilizzando Task.Delay
a cui passare un valore di timeout e il token cancellazione per sostenere questi due comportamenti secondo le seguenti linee:
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;
}
Tuttavia, questo ha tutti i tipi di problemi tra cui potenziali condizioni di gara tra l'attività di ritardo e il gestore MessageReceived
. Non sono mai stato in grado di far funzionare questo approccio in modo affidabile, inoltre sembra ridicolmente complicato e un uso inefficiente del pool di thread. È laborioso, soggetto a errori e fa male alla testa.
Nota a margine: sono l'unica persona confusa dall'API DatagramSocket
in generale? Non solo sembra essere un brutto conglomerato del modello IAsyncAction
WinRT e TPL con qualche EAP complicato, non mi sento molto a mio agio con un'API che intende rappresentare un protocollo fondamentalmente senza connessione come i metodi UDP che contengono il nome ConnectAsync
in loro. Questa sembra essere una contraddizione in termini per me.
Invece del secondo metodo, creare un nuovo metodo che combina l'attività dal primo con Task.Delay e utilizzi Task.WhenAny. Una volta restituito, l'operazione o il timeout si sono completati. –