In Task Parallel Library: How to defer Task.TaskFactory.FromAsync task execution?
I have a method that returns a task like:
public static Task<int> SendAsync(this Socket socket, byte[] buffer, int offset, int count)
{
if (socket == null) throw new ArgumentNullException("socket");
if (buffer =开发者_StackOverflow社区= null) throw new ArgumentNullException("buffer");
return Task.Factory.FromAsync<int>(
socket.BeginSend(buffer, offset, count, SocketFlags.None, null, socket),
socket.EndSend);
}
I would like to keep a reference to the task and run it later. However it seems like the task created by the FromAsync method is executed immediatly. How can I defer it's execution?
If your interface requires that you return a task, you may end up unnecessarily scheduling work on the thread pool just to make the BeginSend() call (this is what is happening in the previous answer where the FromAsync() call is wrapped by another Task).
Instead, if you are able to change the interface, you could use a standard delayed execution technique such as:
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);
}
The caller would invoke the result to start the operation (i.e. call FromAsync/BeginSend) and use ContinueWith() to process the result:
Func<Task<int>> sendAsync = socket.SendAsync(buffer, offset, count);
sendAsync().ContinueWith(
antecedent => Console.WriteLine("Sent " + antecedent.Result + " bytes."));
If exposing Func<> in your interface is not appropriate, you could wrap it into a separate class:
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));
}
And the caller would look like:
DelayedTask<int> task = socket.SendAsync(buffer, offset, count);
task.Start().ContinueWith(
antecedent => Console.WriteLine("Sent " + antecedent.Result + " bytes."));
For starters, if you look at the syntax, you'll realize that you're actually invoking the BeginSend
method yourself and causing it to return the IAsyncResult
for the first parameter of FromAsync. That's just one of the overloads of FromAsync though. If you look there's other overloads and the ones you're looking for are the ones that take Func<...>
instead. Unfortunately these overloads will also invoke the method right away on your behalf due to the fact that, under the covers, what's really happening is that FromAsync is just wrapping the APM invocation pattern using a TaskCompletionSource<TResult>
.
The only way I can actually see you being able to defer the execution is to actually wrap the FromAsync
work up in a parent task which you don't Start
yourself. For example:
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;
}
}
Now a caller can get the task like so:
Task<int> task = SendAsync(...);
and until they call task.Start()
the work would not begin.
Wrapping the task with another task can defer the execution.
// 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);
精彩评论