2011-11-30 26 views
2

我正在设计一个API,公开一个可取消的Task,并且想确保我已经正确地设计了它。是否有暴露Task s的标准模式(可能类似于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); 
    } 
} 

回答

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以始终返回已运行的任务

这是为了防止出现问题,就像你在上面的代码中有一个:

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

在这里,你”实际上调用启动的是错误的任务 - 不是原来的任务,而是延续...一个典型的方法会使“运行”返回一个热门任务,这将允许你写这个:

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

再一次,这在你的第二个例子中也是如此。你可以Task.Factory.StartNew,并看看MyAPI.Run,但从来没有实际上呼吁Start内的任务...因此,这些任务永远不会开始,永远不会完成。第二,我会建议使用.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); 
} 
+0

关于你的Test2重写,会发生什么异常处理?具体来说,通过父母/孩子的安排,我会'尝试'父母并且抓住子女抛出的任何东西的'AggergateException'。这将如何工作在你的重写? – nmarler

+0

@nmarler你必须在继续处理它 - 你可以明确地将一个Try/catch包围在一个调用中,以便继续处理错误,如果需要的话。 –

+0

我正在考虑只返回'热'任务的概念。在我的应用程序中,我有一个“SequenceManager”对象,负责运行其他类中的一些任务。当SequenceManager被初始化时,我的意图是以某种安排(某些顺序相互连接,某些异步)组成任务子对象任务。然后,在将来的某个时刻,可以运行这个“主”任务。我认为现在应该简化SequenceManager init,而是暴露一个RunAsync方法,它将即时组成子RunAsyncs。谢谢,你的建议是非常有帮助的 – nmarler