我正在设计一个公开可取消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 );
}
}
答案 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 );
}
这很危险 - 返回Task
或Task<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中出现的新命名约定,仅用于将来的验证。这是为了使返回Task
或Task<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);
}