In Libreria Task Parallel: come rinviare l'esecuzione dell'attività Task.TaskFactory.FromAsync?
-
26-09-2019 - |
Domanda
Ho un metodo che restituisce un compito come:
public static Task<int> SendAsync(this Socket socket, byte[] buffer, int offset, int count)
{
if (socket == null) throw new ArgumentNullException("socket");
if (buffer == null) throw new ArgumentNullException("buffer");
return Task.Factory.FromAsync<int>(
socket.BeginSend(buffer, offset, count, SocketFlags.None, null, socket),
socket.EndSend);
}
Vorrei mantenere un riferimento al compito ed eseguirlo in un secondo momento. Tuttavia sembra che l'attività creata dal metodo FromAsync viene eseguito immediatamente. Come posso rinviarlo di esecuzione?
Soluzione
Per cominciare, se si guarda alla sintassi, vi renderete conto che si sta effettivamente richiamare il metodo BeginSend
te stesso e inducendolo a restituire il IAsyncResult
per il primo parametro di FromAsync. Questo è solo uno degli overload di FromAsync però. Se si guarda ci sono altri sovraccarichi e quelli che state cercando sono quelli che prendono Func<...>
invece. Purtroppo questi sovraccarichi sia anche invocare il metodo subito a vostro nome a causa del fatto che, sotto le coperte, cosa sta realmente accadendo è che FromAsync è solo avvolgendo il modello APM invocazione utilizzando un TaskCompletionSource<TResult>
.
L'unico modo che posso effettivamente vedere voi essere in grado di rinviare l'esecuzione è quello di avvolgere in realtà il lavoro FromAsync
in un'attività principale, che non si Start
te stesso. Ad esempio:
public static Task<int> SendAsync(this Socket socket, byte[] buffer, int offset, int count)
{
if (socket == null) throw new ArgumentNullException("socket");
if (buffer == null) throw new ArgumentNullException("buffer");
return new Task<int>(() =>
{
return Task.Factory.FromAsync<int>(
socket.BeginSend(buffer, offset, count, SocketFlags.None, null, socket),
socket.EndSend).Result;
}
}
Ora un chiamante può ottenere il compito in questo modo:
Task<int> task = SendAsync(...);
e fino a quando lo chiamano task.Start()
il lavoro non avrebbe avuto inizio.
Altri suggerimenti
Se l'interfaccia richiede che ritorni un compito, si può finire il lavoro di pianificazione inutilmente sul pool di thread solo per effettuare la chiamata BeginSend () (questo è ciò che sta accadendo nella risposta precedente in cui il () chiamata FromAsync è avvolto da un'altra attività).
Al contrario, se si è in grado di cambiare l'interfaccia, è possibile utilizzare una tecnica di esecuzione ritardata standard come:
public static Func<Task<int>> SendAsync(this Socket socket, byte[] buffer, int offset, int count)
{
if (socket == null) throw new ArgumentNullException("socket");
if (buffer == null) throw new ArgumentNullException("buffer");
return () =>
Task.Factory.FromAsync<int>(
socket.BeginSend(buffer, offset, count, SocketFlags.None, null, socket),
socket.EndSend);
}
Il chiamante potrebbe richiamare il risultato per avviare l'operazione (cioè chiamata FromAsync / BeginSend) e usare ContinueWith () per elaborare il risultato:
Func<Task<int>> sendAsync = socket.SendAsync(buffer, offset, count);
sendAsync().ContinueWith(
antecedent => Console.WriteLine("Sent " + antecedent.Result + " bytes."));
Se esponendo Func <> nella vostra interfaccia non è appropriato, si potrebbe avvolgere in una classe separata:
public class DelayedTask<TResult>
{
private readonly Func<Task<TResult>> func;
public DelayedTask(Func<Task<TResult>> func)
{
this.func = func;
}
public Task<TResult> Start()
{
return this.func();
}
}
public static DelayedTask<int> SendAsync(this Socket socket, byte[] buffer, int offset, int count)
{
if (socket == null) throw new ArgumentNullException("socket");
if (buffer == null) throw new ArgumentNullException("buffer");
return new DelayedTask<int>(() =>
Task.Factory.FromAsync<int>(
socket.BeginSend(buffer, offset, count, SocketFlags.None, null, socket),
socket.EndSend));
}
E il chiamante sarà simile:
DelayedTask<int> task = socket.SendAsync(buffer, offset, count);
task.Start().ContinueWith(
antecedent => Console.WriteLine("Sent " + antecedent.Result + " bytes."));
Avvolgere il compito con un altro compito può rinviare l'esecuzione.
// Wrap the task
var myTask = new Task<Task<int>>(() => SendAsync(...));
// Defered start
Thread.Sleep(1000);
myTask.Start();
// Thread is not blocked
Console.WriteLine("Started");
// Wait for the result
Console.WriteLine(myTask.Unwrap().Result);