我昨天问了一个问题,不幸的是,即使有了提供的答案,我仍然在如何正确地做事情的绊脚石…我的问题是,我的代码实际上可以工作,但我是并发编程的完全新手,感觉我没有以正确的方式编程,最重要的是,我害怕养成坏习惯。
举一个简单的例子来阐述昨天的问题,假设我有以下方法:
static Task<IEnumerable<MyClass>> Task1(CancellationToken ct)
static Task<IEnumerable<int>> Task2(CancellationToken ct, List<string> StringList)
static Task<IEnumerable<String>> Task3(CancellationToken ct)
static Task<IEnumerable<Double>> Task4(CancellationToken ct)
static Task Task5(CancellationToken ct, IEnumerable<int> Task2Info, IEnumerable<string> Task3Info, IEnumerable<double> Task4Info)
static Task Task6(CancellationToken ct, IEnumerable<int> Task2Info, IEnumerable<MyClass> Task1Info)
我编写的使用它们的代码如下:
static Task execute(CancellationToken ct)
{
IEnumerable<MyClass> Task1Info = null;
List<string> StringList = null;
IEnumerable<int> Task2Info = null;
IEnumerable<string> Task3Info = null;
IEnumerable<double> Task4Info = null;
var TaskN = Task.Run(() =>
{
Task1Info = Task1(ct).Result;
}
, ct)
.ContinueWith(res =>
{
StringList = Task1Info.Select(k=> k.StringVal).ToList();
Task2Info = Task2(ct, StringList).Result;
}
, ct);
return Task.Run(() =>
{
return Task.WhenAll
(
TaskN,
Task.Run(() => { Task3Info = Task3(ct).Result; }, ct),
Task.Run(() => { Task4Info = Task4(ct).Result; }, ct)
)
.ContinueWith(res =>
{
Task5(ct, Task2Info, Task3Info, Task4Info).Wait();
}
, ct)
.ContinueWith(res =>
{
Task6(ct, Task2Info, Task1Info).Wait();
}
, ct);
});
}
也就是说:
- 我需要
Task1
的结果来计算StringList
并运行Task2
-
Task2
、Task3
和Task4
可以同时运行 - 我需要上述所有方法的返回值用于以后的方法调用
- 一旦这些运行,我使用它们的结果运行
Task5
- 一旦
Task5
运行,我使用运行Task6
的所有结果
作为一个简单的解释,假设第一部分是数据收集,第二部分是数据清理,第三部分是数据报告
就像我说的,我的挑战是这实际上是运行的,但我只是觉得它更像是一个"hack",而不是正确的编程方式——并发编程对我来说是非常新的,我当然想学习最好的方法来完成它…
如果我能看到你的TaskN
方法是如何实现的,我会对我的答案感觉更好。具体来说,我想验证是否需要将TaskN
方法调用包装在对Task.Run()
的调用中,如果它们已经返回Task
返回值。
但就我个人而言,我只会使用async-await
风格的编程。我发现它相当容易读/写。文档可以在这里找到。
我会设想你的代码看起来像这样:
static async Task execute(CancellationToken ct)
{
// execute and wait for task1 to complete.
IEnumerable<MyClass> Task1Info = await Task1(ct);
List<string> StringList = Task1Info.Select(k=> k.StringVal).ToList();
// start tasks 2 through 4
Task<IEnumerable<int>> t2 = Task2(ct, StringList);
Task<IEnumerable<string>> t3 = Task3(ct);
Task<IEnmerable<Double>> t4 = Task4(ct);
// now that tasks 2 to 4 have been started,
// wait for all 3 of them to complete before continuing.
IEnumerable<int> Task2Info = await t2;
IEnumerable<string> Task3Info = await t3;
IEnumerable<Double> Task4Info = await t4;
// execute and wait for task 5 to complete
await Task5(ct, Task2Info, Task3Info, Task4Info);
// finally, execute and wait for task 6 to complete
await Task6(ct, Task2Info, Task1Info);
}
我不完全理解问题中的代码,但是通过依赖关系链接任务非常容易。例子:
var t1 = ...;
var t2 = ...;
var t3 = Task.WhenAll(t1, t2).ContinueWith(_ => RunT3(t1.Result, t2.Result));
你可以像这样表达任意的依赖DAG。这也使得不需要存储到局部变量中,尽管如果需要的话可以这样做。
这样你也可以摆脱尴尬的Task.Run(() => { Task3Info = Task3(ct).Result; }, ct)
模式。这与Task3(ct)
相同,除了存储到局部变量,而局部变量可能一开始就不应该存在。
.ContinueWith(res =>
{
Task5(ct, Task2Info, Task3Info, Task4Info).Wait();
}
这可能应该是
.ContinueWith(_ => Task5(ct, Task2Info, Task3Info, Task4Info)).Unwrap()
有帮助吗?请留言
感觉我没有以正确的方式编程,最重要的是,我害怕养成坏习惯。
我很想学习做这件事的最好方法…
首先,区分异步和并行,它们是并发的两种不同形式。一个操作可以很好地匹配"异步"操作。如果它不需要线程来完成它的工作-例如,I/O和其他逻辑操作,您等待定时器之类的东西。"Parallelism"是关于在多个核心上分割CPU限制的工作-如果你的代码在一个CPU上达到100%的最大利用率,并且你希望它通过使用其他CPU来运行得更快,你需要并行性。
接下来,遵循一些指导原则,哪些api在哪些情况下使用。Task.Run
用于将cpu绑定的工作推送到其他线程。如果您的工作不受cpu限制,则不应该使用Task.Run
。Task<T>.Result
和Task.Wait
更偏向于平行;除了少数例外,它们实际上应该只用于动态任务并行。动态任务并行性非常强大(正如@usr所指出的,您可以表示任何DAG),但它也很低级,使用起来很笨拙。几乎总是有更好的方法。ContinueWith
是另一个动态任务并行API的例子。
因为你的方法签名返回Task
,我将假设它们是自然异步的(意思是:它们不使用Task.Run
,并且可能是用I/O实现的)。在这种情况下,Task.Run
是不正确的工具。适合异步工作的工具是async
和await
关键字。
所以,一次一个地采取你想要的行为:
// I need the results of Task1 to calculate StringList and to run Task2
var task1Result = await Task1(ct);
var stringList = CalculateStringList(task1Result);
// Task2, Task3 and Task4 can all run concurrently
var task2 = Task2(ct, stringList);
var task3 = Task3(ct);
var task4 = Task4(ct);
await Task.WhenAll(task2, task3, task4);
// I need the return values from all of the above for later method calls
var task2Result = await task2;
var task3Result = await task3;
var task4Result = await task4;
// Once these are run, I use their results to run Task5
await Task5(ct, task2Result, task3Result, task4Result);
// Once Task5 is run, I use all the results in running Task6
await Task6(ct, task2Result, task1Result);
关于什么api适合在什么情况下使用的更多信息,我在我的博客上有一个任务指南,我在我的书中介绍了许多并发性最佳实践。