3

First off, apologies for the title; I couldn't really think of how to succinctly articulate what I'm trying to do.

I have the following two functions:

Main code:

private async Task<PreparationInfo> PrepareRoundtrip()
{
    PreparationInfo output = new PreparationInfo();

    Task.Delay(3000); // stands in for a call to the server to fetch data for how to prepare

    prepare(output) // package result into output

    return output;
}

private Task ExecuteWithPrepare()
{
    if (!PrepareEnabled) // instance variable
        return stateObject.Execute();
    else
    {
        Task<PreparationInfo> prepareTask = PrepareRoundtrip();
        return tceTask.ContinueWith((prepareTaskInternal) =>
        {
            stateObject.Execute(); // this is the Task that I need to return
        });
    }
}

stateObject.Execute() header:

internal Task Execute()
{
    ...
}

I'm writing a wrapper for the stateObject.Execute() method that will optionally call a preparation method (PrepareRoundtrip()) beforehand to process some parameters before executing.

If preparation is not enabled (PrepareEnabled == false), I can just call Execute() direction and immediately return the Task it returns. If preparation is enabled, I need to run the preparation method (which is unique to this task, I can change it however necessary), then run Execute().

The part I'm stuck on is this:

The entire function needs to run and return as though stateObject.Execute() was called directly, just with the PrepareRoundtrip() part added, meaning two things:

  1. The Task that gets returned from ExecuteWithPrepare() needs to represent the Task that stateObject.Execute() returns.

  2. ExecuteWithPrepare() needs to return immediately (i.e. not wait for the 3 second delay in PrepareRoundtrip()

What's the best way to achieve this? Thanks!

TL;DR:

Adding a wrapper for stateObject.Execute() to add an extra preparation step that's potentially lengthy; need the whole thing to return a Task representing the original result immediately rather than waiting for the preparation step to complete first.

Benjin
  • 2,264
  • 2
  • 25
  • 50

2 Answers2

1
  1. Use Unwrap to turn a Task<Task> (which is what you have there) into a Task representing the completion of the inner Task, without synchronously waiting for the outer task to finish.

  2. Just await the Task<Task> twice, instead of once, if in an async method.

Servy
  • 202,030
  • 26
  • 332
  • 449
0

You shouldn't use ContinueWith at all. It's an outdated method with dangerous behavior (specifically, it will use the current task scheduler).

Instead, just use await:

private Task ExecuteWithPrepareAsync()
{
  if (!PrepareEnabled)
    return stateObject.ExecuteAsync();
  else
    return PrepareAndExecuteAsync();
}

private async Task PrepareAndExecuteAsync()
{
  await PrepareRoundtripAsync();
  await stateObject.ExecuteAsync();
}

Also note the Async naming convention, part of the TAP pattern.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • `stateObject.Execute()` is not an async method despite returning a Task; should it still be named `ExecuteAsync`? It was written before async-await was a thing. I _really_ wish I could rewrite the entire stack to properly use async-await instead of this bastardized and outdated Task structure... – Benjin Dec 16 '14 at 01:13
  • @Benjin: If it is compatible with `await`, then it should be named `ExecuteAsync`. – Stephen Cleary Dec 16 '14 at 01:53
  • 1
    I explain more in depth [on my blog (see example at the end)](http://blog.stephencleary.com/2013/08/startnew-is-dangerous.html). The problem is with how the code is used: for most code, `ContinueWith`-style code will either run on the thread pool or a UI *TaskScheduler* - but `await`-style code will either run on the thread pool or a UI *SynchronizationContext*. `await` is safer because it captures the SynchronizationContext in preference to the TaskScheduler. – Stephen Cleary Dec 16 '14 at 14:21
  • @Benjin If the method returns a `Task`, then it's asynchronous, and the `Async` suffix is appropriate. How it goes about making itself asynchronous (whether it uses `await` or not) is an implementation detail of that method not relevant to its callers. – Servy Dec 16 '14 at 14:55