公开可取消的任务

时间:2011-11-30 19:43:23

标签: multithreading .net-4.0 task-parallel-library

我正在设计一个公开可取消Task的API,并希望确保我已正确设计它。是否存在用于公开Task的标准模式(可能类似于APM BeginXxx / EndXxx模式)?有什么改进建议吗?见MyAPI.Run

Test2是否展示了并行运行多个MyAPI.Run任务的最佳方式?

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 );
    }
}

1 个答案:

答案 0 :(得分:2)

您的实施中存在一些我会考虑更改的问题。

首先,这个方法:

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

这很危险 - 返回TaskTask<T>的API的约定是返回“热门”任务。这意味着您应该设计API以始终返回已在运行的任务

这是为了防止出现问题,例如上面代码中的问题:

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

如果你看一下,可以改写为:

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

在这里,你实际上是在错误的任务上调用Start - 而不是原始任务,而是继续......一个典型的方法会让“Run”返回一个热门任务,这将允许你把它写成: / p>

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

同样,这也是你的第二个例子。你可以Task.Factory.StartNew,让它看看MyAPI.Run,但实际上从不在内部的任务上调用Start ......因此,那些的任务永远不会启动,也永远不会完成< / em>的

其次,我建议使用在.NET 4.5中出现的新命名约定,仅用于将来的验证。这是为了使返回TaskTask<T>的例程的名称带有Async后缀。在这种情况下,它将是MyAPI.RunAsync。这将使您的代码看起来像将在下一版.NET中出现的框架代码。

最后,关于你的上一个问题:

  

Test2是否展示了并行运行多个MyAPI.Run任务的最佳方法?

实际上,如果你让Run方法返回一个正在运行的任务,这就变得简单了:

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);
}