I asked a question yesterday,不幸的是,即使提供了答案,我仍然会遇到关于如何正确处理事情的绊脚石...我的问题是我的代码确实有效,但我是一个在并发编程方面是完全新手,感觉我没有正确的编程方式,最重要的是,我害怕养成坏习惯。
为了详细阐述昨天的问题,假设我有以下方法:
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
作为一个简单的解释,想象第一部分是数据收集,第二部分是数据清理和第三部分数据报告
就像我说的那样,我的挑战是它实际运行,但我觉得这更像是一种“黑客”正确的编程方式 - 并发编程对我来说是一个新手,我当然希望学习最好的方法应该这样做......
答案 0 :(得分:3)
如果我能看到你的TaskN
方法是如何实现的,我会对自己的答案感觉更好。具体来说,如果他们已经返回TaskN
返回值,我希望验证您的Task.Run()
方法调用是否需要包含在Task
的调用中。
但就个人而言,我只会使用async-await
编程风格。我发现它很容易读/写。可以找到文档here。
然后我会想到你的代码看起来像这样:
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);
}
答案 1 :(得分:2)
我不完全理解有问题的代码,但通过依赖关系链接任务非常容易。例如:
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()
这有帮助吗?发表评论。
答案 2 :(得分:2)
感觉我的编程方式不正确,最重要的是,我害怕养成坏习惯。我绝对想学习应该做的最佳方法......
首先,区分异步和 parallel ,它们是并发的两种不同形式。如果操作不需要线程来完成其工作,则操作是“异步”的良好匹配 - 例如,I / O和其他逻辑操作,其中等待类似计时器的操作。 “并行性”是指在多个内核之间拆分CPU绑定工作 - 如果您的代码以100%最大化一个CPU并且希望使用其他CPU运行得更快,则需要并行性。
接下来,请遵循一些指南,了解在哪些情况下使用哪些API。 Task.Run
用于将CPU绑定的工作推送到其他线程。如果您的工作不受CPU限制,则不应使用Task.Run
。 Task<T>.Result
和Task.Wait
在并行方面更为明显;除了少数例外,它们应该只用于dynamic task parallelism。动态任务并行性非常强大(正如@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在哪些情况下适合的更多信息,我的博客上有Tour of Task,我在my book中介绍了很多并发最佳做法。