Question

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 == 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?

Was it helpful?

Solution

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.

OTHER TIPS

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."));

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);
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top