Expose a cancellable Task

I'm designing an API that exposes a cancellable Task, and want to make sure I have designed it correctly. Is there a standard pattern for exposing Tasks (maybe something similar to the APM BeginXxx/EndXxx pattern)? Any suggestions to improve? See MyAPI.Run

Does Test2 demonstrate the best way to run many MyAPI.Run tasks in parallel?

public static class MyAPI
{
    public static Task<MyResult> Run( CancellationToken token ) {
        // lazily create Task, so as to include the specified CancellationToken
        return new Task<MyResult>( MyPrivateAsyncMethod, token, token );
    }

    private static MyResult MyPrivateAsyncMethod( object state ) {
        CancellationToken ct = (CancellationToken)state;
        ct.ThrowIfCancellationRequested();
        return new MyResult();
    }
}

public static class TestMyAPI
{
    // User can start the Task directly
    public static void Test1() {
        CancellationTokenSource cts = new CancellationTokenSource();
        MyAPI.Run( cts.Token )
            .ContinueWith( task => Console.WriteLine( task.Result.ToString() ) )
            .Start();
    }

    // User must wrap in new Tasks to get Parent/Child relationship
    public static void Test2() {
        CancellationTokenSource cts = new CancellationTokenSource();
        Task.Factory.StartNew( () =>
        {
            var childTasks = new[] {
                Task.Factory.StartNew<MyResult>( () => MyAPI.Run( cts.Token ).Result, cts.Token, TaskCreationOptions.AttachedToParent, TaskScheduler.Default ),
                Task.Factory.StartNew<MyResult>( () => MyAPI.Run( cts.Token ).Result, cts.Token, TaskCreationOptions.AttachedToParent, TaskScheduler.Default )
            };

            Task.Factory
                .ContinueWhenAll<MyResult>( childTasks, tasks => { foreach( var task in tasks ) task.ToString(); } )
                .Start();
        }, cts.Token );
    }
}

Answers


There are a couple of issues in your implementation that I would consider changing.

First off, this method:

public static Task<MyResult> Run( CancellationToken token ) {
    // lazily create Task, so as to include the specified CancellationToken
    return new Task<MyResult>( MyPrivateAsyncMethod, token, token );
}

This is dangerous - The convention for APIs returning Task or Task<T> is to return a "hot" Task. This means that you should design your APIs to always return a task which is already running.

This is to prevent problems, like the one you have in your code above:

    MyAPI.Run( cts.Token )
        .ContinueWith( task => Console.WriteLine( task.Result.ToString() ) )
        .Start();

If you look at this, it can be rewritten as:

    Task<MyResult> original = MyAPI.Run( cts.Token );
    Task second = original.ContinueWith( task => Console.WriteLine( task.Result.ToString() ) );
    second.Start();

Here, you're actually calling Start on the wrong task - not the original task, but the continuation... A typical approach would make "Run" return a hot task, which would allow you to write this as:

    MyAPI.Run( cts.Token )
         .ContinueWith( task => Console.WriteLine( task.Result.ToString() ) );

Again, this rears its head in your second example, as well. You can Task.Factory.StartNew, and have it look at MyAPI.Run, but never actually call Start on the tasks inside... As such, those tasks will never start and never complete.

Second, I would recommend using the new conventions for naming that are showing up in .NET 4.5, just for future proofing. This is to make routines that return Task or Task<T> have the name with an Async suffix. In this case, it would be MyAPI.RunAsync. This will allow your code to look like the framework code that will crop up in the next release of .NET.

Finally, in regards to your last question:

Does Test2 demonstrate the best way to run many MyAPI.Run tasks in parallel?

Actually, if you make the Run method return a running task, this becomes far simpler:

public static void Test2() {
    CancellationTokenSource cts = new CancellationTokenSource();
    var task1 = MyAPI.RunAsync(cts.Token);
    var task2 = MyAPI.RunAsync(cts.Token);

    Task.Factory.ContinueWhenAll<MyResult>(
       new[] { task1, task2 }, 
       tasks => { 
           foreach( var task in tasks ) task.ToString(); 
       }, 
       cts.Token);
}

Need Your Help

How to refresh JTable Model without overwriting the old model in the view?

swing jtable repaint java-6 jsr296

I'm developing a Desktop Application in java(jdk 1.6) with swing but having a problem after refreshing the view. Whenever I update the Model of a JTable, I see that the data in this model changes b...

About UNIX Resources Network

Original, collect and organize Developers related documents, information and materials, contains jQuery, Html, CSS, MySQL, .NET, ASP.NET, SQL, objective-c, iPhone, Ruby on Rails, C, SQL Server, Ruby, Arrays, Regex, ASP.NET MVC, WPF, XML, Ajax, DataBase, and so on.