如何在ASP.NET中并行运行多个方法

3
我是一名有用的助手,可以为您翻译文本。

我在一个ASP.NET控制台应用程序中有8个方法,例如Fun1()Fun2()Fun3()等等。在我的控制台应用程序中,我已经按顺序调用了所有这些方法。但现在的要求是使用并行编程概念来完成。我已经阅读了Java中的任务和线程概念,但对.NET并行编程完全不熟悉。

以下是我需要在控制台应用程序中使用的方法流程:

Diagram

正如您所看到的图表,Task1Task2应该并行运行,而Task3将在前两个任务完成后才会发生。
每个任务内部的函数,例如Task1Fun3Fun4,应该按顺序依次运行。
请问有人能帮我吗?

1
据我所知,控制台应用程序和ASP.NET应用程序是两种不同类型的项目。在ASP.NET控制台应用程序中... - Theodor Zoulias
Fun4 应该在 Fun3 完成后运行,还是与 Fun3 并发运行? - Theodor Zoulias
2
@TheodorZoulias 不完全是这样。从ASP.NET Web API开始,继续到ASP.NET Core,能力内置于“自托管” - 您只需运行为控制台应用程序即可。您可以从控制台模板开始构建到需要的ASP.NET Core,或者从ASP.NET Core模板开始并删除所有内容,直到剩下一个小型控制台应用程序。这可能不是您在生产中所做的方式,但Web和控制台应用程序之间的界限确实非常模糊。与旧的基于.NET Framework的ASP.NET非常不同。 - mason
@TheodorZoulias 不不,Fun4 可以在 Fun3 完成后运行。 - Suprateem Bose
1
@SuprateemBose 我编辑了问题以澄清这一点。 - Theodor Zoulias
显示剩余2条评论
2个回答

3

解决这个问题的一种方法是使用WhenAll

以一个示例来说明,我创建了X个名称为FuncX()的方法,如下所示:

async static Task<int> FuncX()
{
    await Task.Delay(500);
    var result = await Task.FromResult(1);
    return result;
}

在这种情况下,我们有Func1、Func3、Func4、Func5和Func6。
因此,我们调用方法并将它们传递给Task列表。
var task1 = new List<Task<int>>();

task1.Add(Func3());
task1.Add(Func4());

var task2 = new List<Task<int>>();

task2.Add(Func1());
task2.Add(Func5());
task2.Add(Func6());

您有两个选项可以获得结果:
// option1
var eachFunctionIsDoneWithAwait1 = await Task.WhenAll(task1);
var eachFunctionIsDoneWithAwait2 = await Task.WhenAll(task2);
var sum1 = eachFunctionIsDoneWithAwait1.Sum() + eachFunctionIsDoneWithAwait2.Sum();
Console.WriteLine(sum1);

// option2
var task3 = new List<List<Task<int>>>();
task3.Add(task1);
task3.Add(task2);
var sum2 = 0;
task3.ForEach(async x =>
{
    var r = await Task.WhenAll(x);
    sum2 += r.Sum();
});

Console.WriteLine(sum2);

这只是一个启示性的例子,你可以根据自己的需求进行更改。


3

以下是如何使用 Task.Run 方法根据图表创建任务的方法:

Task task1 = Task.Run(() =>
{
    Fun3();
    Fun4();
});
Task task2 = Task.Run(() =>
{
    Fun1();
    Fun5();
    Fun6();
});
Task task3 = Task.Run(async () =>
{
    await Task.WhenAll(task1, task2);
    Fun7();
    Fun8();
});

Task.Run 会在 ThreadPool 上调用委托,而不是在专用线程上调用。如果您有某些原因需要为每个任务创建专用线程,则可以使用高级的 Task.Factory.StartNew 方法,并使用 TaskCreationOptions.LongRunning 参数,如 这里 所示。

需要注意的是,上述实现在异常情况下并不具有最优行为。如果Fun3()立即失败,最优行为应该尽快停止task2的执行。但是,这个实现将在传播错误之前执行所有三个函数Fun1Fun5Fun6。你可以通过创建一个CancellationTokenSource并在每个函数后调用Token.ThrowIfCancellationRequested来修复这个小缺陷,但这会很麻烦。
另一个问题是,如果task1task2都失败了,只有task1的异常会通过task3传播。解决这个问题并不是简单的。

更新:这里有一种解决部分异常传播问题的方法:

Task task3 = Task.WhenAll(task1, task2).ContinueWith(t =>
{
    if (t.IsFaulted)
    {
        TaskCompletionSource tcs = new();
        tcs.SetException(t.Exception.InnerExceptions);
        return tcs.Task;
    }
    if (t.IsCanceled)
    {
        TaskCompletionSource tcs = new();
        tcs.SetCanceled(new TaskCanceledException(t).CancellationToken);
        return tcs.Task;
    }
    Debug.Assert(t.IsCompletedSuccessfully);
    Fun7();
    Fun8();
    return Task.CompletedTask;
}, default, TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default)
    .Unwrap();

如果task1task2都失败了,task3将传播这两个任务的异常。

我不太理解为什么 Task.WhenAll() 只会抛出来自 task1 的异常。根据 MSDN 文档 - 如果任何一个提供的任务以故障状态完成,则返回的任务也将以 Faulted 状态完成,其中其异常将包含来自每个提供的任务的未包装异常集合的聚合- 那么它不应该也抛出来自 task2 的异常吗? - Qwertyluk
1
@Qwertyluk,Task.WhenAll 会传播所有异常,但 await 只会传播第一个异常 - Theodor Zoulias

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接