-2

In the process of refactoring some legacy code at work and part of it is to convert an instance of List<Action> to List<Func<Task>>. Elsewhere in the codebase, there are several instances of something along the lines of this:

entity.Tasks.Add(() => 
{
    _service.Process(operation)
}

The above would work fine when the Tasks property was List<Action>, but gives errors when turned into List<Func<Task>> with no obvious way to fix it.

I know next to nothing about Func, Task, Action, asynchronous/synchronous programming, particularly in C#. Any help provided is immensely appreciated or even just a referral to information where I could find the information needed to solve this myself.

UW_Huskies_01
  • 71
  • 1
  • 10

3 Answers3

1

What you have to do is to create a function that returns a Task. A solution could be creating a task that internally executes your synchronous code and then add it to the list. Like this:

entity.Tasks.Add(() => Task.Run(_service.Process(operation)));

Just note that the code only creates a function that returns a Task but the Task won't be executed until you ask for it.

Here is more info about the topic:

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
Carlosoj
  • 164
  • 2
  • 8
0

A quick primer...

List<Action> says: I'm a list that accepts a method with no input parameters and returns void when invoked.

List<Func<Task>> says: I'm a list that accepts a method with no input parameters and returns a Task when invoked.

Note: I'm using the term "method" to represent a named method, delegate, lambda, etc...

For your case, entity.Tasks was originally defined as List<Action>; but is now defined as List<Func<Task>>.

Given the case where you need to take the legacy methods which return no value (void), you could create a shimming method which returns a task after completing the Action from the original method.

For Example...

static Task Shim( Action action )
{
    action();
    return Task.CompletedTask;
}

// the above will wrap a method that does not return a value such as:
static void DoWork( string str )
{
    Debug.WriteLine( str );
}

You can now use the Shim to add to your new collection type. For example...

var list = new List<Func<Task>>();

// add the Shim to the list
list.Add( () => Shim( () => DoWork( "I'm a shim" ) ));

In your use case, it will look something like this:

entity.Tasks.Add(() => 
{
    return Shim( () => _service.Process(operation) );
}

// or a bit more shorthanded:
entity.Tasks.Add( () => Shim( () => _service.Process(operation) ));

As pointed out in the comments, the Shim path is less than ideal since the new version really should be wrapped in a Task to be more inline with how Tasks and Exceptions get wrapped up.

Here's an example of wrapping the legacy code into the new version without any shims:

entity.Tasks.Add( () => Task.Run( () => _service.Process(operation) ));
Metro Smurf
  • 37,266
  • 20
  • 108
  • 140
  • The `Shim` method doesn't have the ideal error behavior. In case the `action` fails, the exception will be thrown synchronously instead of being stored in the resulting `Task`. – Theodor Zoulias Nov 04 '22 at 21:39
  • 1
    Agreed. The `Shim` would retain the same behavior as the legacy version. I'll add another option to wrap the legacy `Action` in a task. – Metro Smurf Nov 04 '22 at 21:55
-2

Actions are your void methods that don't return a response. Your tasks collection is expecting a returned task. You can wrap the _service.Process(operation) with a task and within the task, run the _service.Process, then return something like

return Task.FromResult(false);

CubeRoot
  • 552
  • 2
  • 13