5

I have a series of methods (with variable number of parameters) that return a Task I want to create a method that does something before and after each of this methods, by passing the Task. I've simplified everything (removed cancellationToken, actual processing, etc) in this sample:

public async Task<string> GetDataString()
{
    Console.WriteLine("Executing");
    return "test";
}


public async Task<T> Process<T>(Task<T> task)
{
    Console.WriteLine("Before");
    var res = await task;
    Console.WriteLine("After");
    return res;
}

And in my main:

Task<string> task = GetDataString();
string result = await Process<string>(tasks);
Console.WriteLine(res);

the console output is

Executing
Before
After
test

What can I do to create the task but not actually starting it? And starting it only before the wait?

I managed to do it by creating a PauseToken, as explained in this article: https://devblogs.microsoft.com/pfxteam/cooperatively-pausing-async-methods/ but I wonder if is there a better way.

Thanks, Mattia

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
Mattia Durli
  • 757
  • 7
  • 19
  • Pass a `Func` instead. This method is used throughout the framework. – tia Nov 29 '19 at 10:03
  • Thanks but I thought about that, but I don't have only GetDataString(), I may have GetDataString2(string param) and GetDataString3(string param, int paramInt). How can I create a generic Process(Func) method? – Mattia Durli Nov 29 '19 at 10:09
  • The parameters of the function doesn't matter. – tia Nov 29 '19 at 11:18
  • Yes, I would need a Process(Func), Process(Func) a Process(Func) – Mattia Durli Nov 29 '19 at 11:24
  • @tia you were right that parameters of the functions doesn't matter, I guess I didn't fully understand how Func Delegate works. – Mattia Durli Nov 29 '19 at 16:41

2 Answers2

8

Your generic ProcessAsync method could accept a task factory as argument:

public async Task<T> ProcessAsync<T>(Func<Task<T>> taskFactory)
{
    Console.WriteLine("Before");
    var res = await taskFactory();
    Console.WriteLine("After");
    return res;
}

This way the task will be created at the time you'll invoke the factory method. You are in control of its creation.

Here is an example of calling the ProcessAsync method, passing as factory a lambda:

var result = await ProcessAsync(() => GetDataStringAsync(arg1, arg2));

This way you are not restricted to a factory method without arguments.

For completeness I should mention that Task objects can also created in a cold state using the constructor new Task(), and started later using the Start method, but this approach is not recommended.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
  • Thanks! The solution I was looking for. In the meantime I implemented the Task() one by myself but I didn't like it. What I don't understand is why Func> can pair with GetDataStringAsync(arg1,arg2), shouldn't be declared as Func>? I misunderstood how Func Delegate works – Mattia Durli Nov 29 '19 at 16:39
  • The `GetDataStringAsync` is not invoked directly, because that would be inflexible: `await ProcessAsync(GetDataStringAsync, arg1, arg2)`. You would need a different `ProcessAsync` overload for each set of arguments. Instead it's invoked indirectly through a lambda `() => ...` that captures the values of the arguments. If you want to learn more about captured variables and closures look [here](https://stackoverflow.com/questions/428617/what-are-closures-in-net) or [here](https://stackoverflow.com/questions/9591476/are-lambda-expressions-in-c-sharp-closures). – Theodor Zoulias Nov 29 '19 at 20:24
-1

You can remove the async keyword (from GetDataString) and create a new task which will be executed when you await

so the result of the code below is : before , executing , test , after

      private static async Task Start()
        {
            Task<string> task = GetDataString();
            string result = await Process<string>(task);
            Console.WriteLine(result);

            Console.ReadLine();
        }


        public Task<string> GetDataString()
        {
            return new TaskFactory(TaskScheduler.Default).StartNew(() =>
            {
                Console.WriteLine("Executing");
                return "test";
            });
        }


        public async Task<T> Process<T>(Task<T> task)
        {
            Console.WriteLine("Before");
            var res = await task;
            Console.WriteLine("After");
            return res;
        }
Ramin
  • 87
  • 2
  • 7