locked
TPL API Task Create Methods signature should be different RRS feed

  • Question

  • I think the signature of Create methods in the task class should be something like:

    static Task Create<T>(Action<T> action)
    static Task Create<T>(Action<T> action, T state)

    etc.

    To me it does not make sense to have a generic Action<T> and force the type to be an object in the current API.

    e.g. Task task = Task.Create(new Action<object>(Execute), (object)0);
    instead
    it would look cleaner to have something like -> Task task = Task.Create<int>(new Action<int>(Execute), 0 );

    Was there any compelling reason not do do this?

    Hugo



    Friday, July 11, 2008 1:41 PM

Answers

  • We actually recently went down that path, but backed it out.

     

    As the primary set of overloads for Create (which, by the way, we'll likely rename due to some good feedback from this forum and other places), we plan to support Action (the non-generic one).  With the mainstream languages like C# and Visual Basic now supporting closures, there's less of a need for supporting the delegate,state pattern (like the ThreadPool does), since the closure can capture the relevant information, e.g.

     

    int data = ...;

    Task.Create(() =>

    {

        Use(data);

    });

     

    However, we recognize that for languages that don't support closures, and for some performance-sensitive scenarios, supporting the ability to pass data into a parameterized delegate is important.  Thus we'd like to support Action<T> as you point out.  But we also need to support a variety of overloads, such as what TaskManager to execute on.  For two points, anyone see the problem with these two overloads?

     

    static Task Create(Action action, TaskManager manager);

    static Task Create<T>(Action<T> action, T state);

     

    Spoiler...

     

    The problem is that if you try to call the first one with an action like "delegate { ... }", the C# compiler will use type inference when analyzing the right overload, and will see T == TaskManager, which means these become identical signatures, thus the call is ambiguous.  You can differentiate it with different delegate syntax, but given how common this is, it could be quite confusing.

     

    There's also a performance cost to the Action<T> approach that's more expensive than Action<object>.

     

    We played around with a variety of designs, but in the end, we don't expect Action<object> to be used all that frequently due to closure support, the generic type support really just prevents the developer from having to cast (plus a few other minor things, like an extra boxing operation if the type is a value type), and as such it doesn't seem like a big problem to provide Action<object> instead of Action<T>.

    Friday, July 11, 2008 4:27 PM
    Moderator

All replies

  • We actually recently went down that path, but backed it out.

     

    As the primary set of overloads for Create (which, by the way, we'll likely rename due to some good feedback from this forum and other places), we plan to support Action (the non-generic one).  With the mainstream languages like C# and Visual Basic now supporting closures, there's less of a need for supporting the delegate,state pattern (like the ThreadPool does), since the closure can capture the relevant information, e.g.

     

    int data = ...;

    Task.Create(() =>

    {

        Use(data);

    });

     

    However, we recognize that for languages that don't support closures, and for some performance-sensitive scenarios, supporting the ability to pass data into a parameterized delegate is important.  Thus we'd like to support Action<T> as you point out.  But we also need to support a variety of overloads, such as what TaskManager to execute on.  For two points, anyone see the problem with these two overloads?

     

    static Task Create(Action action, TaskManager manager);

    static Task Create<T>(Action<T> action, T state);

     

    Spoiler...

     

    The problem is that if you try to call the first one with an action like "delegate { ... }", the C# compiler will use type inference when analyzing the right overload, and will see T == TaskManager, which means these become identical signatures, thus the call is ambiguous.  You can differentiate it with different delegate syntax, but given how common this is, it could be quite confusing.

     

    There's also a performance cost to the Action<T> approach that's more expensive than Action<object>.

     

    We played around with a variety of designs, but in the end, we don't expect Action<object> to be used all that frequently due to closure support, the generic type support really just prevents the developer from having to cast (plus a few other minor things, like an extra boxing operation if the type is a value type), and as such it doesn't seem like a big problem to provide Action<object> instead of Action<T>.

    Friday, July 11, 2008 4:27 PM
    Moderator
  • Right, that is a big bummer; type inference is a great at reducing code clutter but not when it gets in the way!!!

     The call is ambiguous between the following methods or properties: 'Example.Create<System.Threading.Tasks.TaskManager>(System.Action<System.Threading.Tasks.TaskManager>, System.Threading.Tasks.TaskManager)' and 'Example.Create(System.Action, System.Threading.Tasks.TaskManager)'   

    You would need to tell C# compiler to use version a specific version:

    Example.Create( new Action(delegate {  }), TaskManager.Current);

    This would be very confusing 100% agreed.

    So another option is to change the method name to something like Create and CreateEx<T>  but it looks ugly and can be confusing, another  option is to or create two implementations of the Task class one with generics and the other not but it may also be confusing.

    Another idea is to reduce the openness of the signature for the type inference to something like this:

    public static Task Create<T>(Action<T> action, ActionState<T> state)
    So ActionState contains the state and it is a specific type, it looks cluttered and not that clear.

    How about something in the middle:
    static Task Create<T>(Action<T> action, object state);

    internaly you can convert the object to the specific type so the delegate recives as the intended type not the object, the only risk is that the user sends an invalid object that can't be casted to the type T.

    This is ages better than what we had beffore, keep up the good work.

    Regards,
    Hugo







     


    Saturday, July 12, 2008 5:07 PM
  • Relying on closure to solve this problem seems like a temporary hack that's waiting for a better solution.  It works, but it's not as simple or slick as it could be.  There are many times that simply specifying a method name as the action would be the simplest approach, but that means we have to use global vars or we're excluded from state parameter passing.

     

    I like one of Hugo's proposals -- introducing a TaskState type seems like a clever way to circumvent the generics ambiguity.  Applying this to the Task.Factory.StartNew() signature or the Task() constructor as such:

     

      public Task<T>( Action<T> function, TaskState<T> state );
      public Task<T,TResult>( Func<T,TResult> function, TaskState<T> state );
    

     

    TaskState<T> would be a lot like the Nullable<T> class -- just a simple wrapper around a Value member.  In practice, using TaskState might look like this:

     

      var myTask = new Task( MyMethod, new TaskState( stateInfo ) );
    
      ...
    
      public void MyMethod( StateInfo stateInfo ) { ... }
    
    

    The TaskState<> solution is not perfect, but it seems like a much better solution than relying on closure of type casting.

     

    Friday, March 11, 2011 9:34 PM