并行运行两个异步任务并在 .NET 4.5 中收集结果
Posted
技术标签:
【中文标题】并行运行两个异步任务并在 .NET 4.5 中收集结果【英文标题】:Run two async tasks in parallel and collect results in .NET 4.5 【发布时间】:2012-09-02 19:28:41 【问题描述】:我一直在尝试获得一些我认为使用 .NET 4.5 会很简单的东西
我想同时启动两个长时间运行的任务并收集 以最佳 C# 4.5 (RTM) 方式实现
以下工作,但我不喜欢它,因为:
我希望Sleep
成为异步方法,以便它可以 await
其他方法
Task.Run()
看起来很笨拙
我认为这根本没有使用任何新的语言功能!
工作代码:
public static void Go()
Console.WriteLine("Starting");
var task1 = Task.Run(() => Sleep(5000));
var task2 = Task.Run(() => Sleep(3000));
int totalSlept = task1.Result + task2.Result;
Console.WriteLine("Slept for a total of " + totalSlept + " ms");
private static int Sleep(int ms)
Console.WriteLine("Sleeping for " + ms);
Thread.Sleep(ms);
Console.WriteLine("Sleeping for " + ms + " FINISHED");
return ms;
无效代码:
更新:这确实有效并且是正确的方法,唯一的问题是Thread.Sleep
此代码不起作用,因为对Sleep(5000)
的调用会立即启动任务运行,因此Sleep(1000)
在完成之前不会运行。这是真的,即使 Sleep
是 async
并且我没有使用 await
或调用 .Result
太快。
我想也许有一种方法可以通过调用async
方法来获得非运行的Task<T>
,这样我就可以在这两个任务上调用Start()
,但我不知道如何获得Task<T>
来自调用异步方法。
public static void Go()
Console.WriteLine("Starting");
var task1 = Sleep(5000); // blocks
var task2 = Sleep(1000);
int totalSlept = task1.Result + task2.Result;
Console.WriteLine("Slept for " + totalSlept + " ms");
private static async Task<int> Sleep(int ms)
Console.WriteLine("Sleeping for " + ms);
Thread.Sleep(ms);
return ms;
【问题讨论】:
注意:让 Go 成为异步方法没有区别 阻塞发生在task1.Result
而不是var task1 = Sleep(5000)
,因为没有 await 关键字的 Sleep 方法是同步的。
【参考方案1】:
虽然您的 Sleep
方法是异步的,但 Thread.Sleep
不是。异步的整个想法是重用单个线程,而不是启动多个线程。因为您已阻止使用对 Thread.Sleep 的同步调用,所以它不会工作。
我假设Thread.Sleep
是您实际想要做的事情的简化。您的实际实现可以编码为异步方法吗?
如果你确实需要运行多个同步阻塞调用,我想看看别处!
【讨论】:
感谢 Richard - 是的,当我实际使用服务调用时,它似乎按预期工作 那么如何异步运行呢?我有应用程序进行大量文件切换并等待文件,大约 5 秒,然后是另一个进程,当我“当所有人”时它首先运行,然后是第二个,即使我说:var x = y()
,而不是@ 987654325@ 或 y().wait()
仍然一直等待,如果 async 不能自行处理,我该怎么办?请注意,y 是用异步装饰的,我希望它在所有时间内完成所有操作,而不是在分配它的位置,编辑:我刚对我的伙伴说,让我们试试Task.Factory
,他说它在什么时候起作用我在这门课中脱颖而出【参考方案2】:
您应该使用 Task.Delay 而不是 Sleep 进行异步编程,然后使用 Task.WhenAll 来组合任务结果。这些任务将并行运行。
public class Program
static void Main(string[] args)
Go();
public static void Go()
GoAsync();
Console.ReadLine();
public static async void GoAsync()
Console.WriteLine("Starting");
var task1 = Sleep(5000);
var task2 = Sleep(3000);
int[] result = await Task.WhenAll(task1, task2);
Console.WriteLine("Slept for a total of " + result.Sum() + " ms");
private async static Task<int> Sleep(int ms)
Console.WriteLine("Sleeping for 0 at 1", ms, Environment.TickCount);
await Task.Delay(ms);
Console.WriteLine("Sleeping for 0 finished at 1", ms, Environment.TickCount);
return ms;
【讨论】:
这是一个很好的答案......但我认为这个错误是答案,直到我运行它。然后我明白了。它确实在 5 秒内执行。诀窍是不要立即等待任务,而是等待 Task.WhenAll。【参考方案3】:这篇文章帮助解释了很多事情。它采用常见问题解答样式。
Async/Await FAQ
这部分解释了为什么 Thread.Sleep
在同一个原始线程上运行 - 导致我最初的困惑。
“async”关键字是否会导致方法的调用排队到 线程池?创建一个新线程?发射火箭飞船 火星?
没有。不,不。请参阅前面的问题。 “异步”关键字 向编译器指示“等待”可以在 方法,使得该方法可以在等待点暂停并具有 当等待的实例时,它的执行异步恢复 完成。这就是为什么如果没有 在标记为“异步”的方法中“等待”。
【讨论】:
【参考方案4】:async Task<int> LongTask1()
...
return 0;
async Task<int> LongTask2()
...
return 1;
...
Task<int> t1 = LongTask1();
Task<int> t2 = LongTask2();
await Task.WhenAll(t1,t2);
//now we have t1.Result and t2.Result
【讨论】:
我 +1 因为你将 t1,t2 声明为 Task,这是正确的方式。 我相信这个解决方案要求 Go 方法也是异步的,这意味着它公开了异步的能力。如果您想要更类似于调用者的Go
方法是同步的,但想要异步完成两个独立任务(即两者都不需要在另一个之前完成,但两者都必须在执行继续之前完成)的情况,那么 @ 987654323@ 会更好,你不需要 await 关键字,因此不需要调用 Go
方法本身是异步的。 这两种方法都不是更好,这只是你的目标的问题。
无效方法:async void LongTask1() ...
没有 Task.Result 属性。在这种情况下使用不带 T 的任务:async Task LongTask1()
.
我没有得到任何一项任务的结果。所以我把它改成了Task<TResult> t1 = LongTask1();
,现在我得到了t1.Result
。 <TResult>
是结果的返回类型。您需要在您的方法中使用 return <TResult>
才能使其正常工作。
值得一提的是,如果您正在做一些非常简单的事情并且不想要额外的t1
和t2
变量,您可以使用new Task(...)
。例如:int int1 = 0; await Task.WhenAll(new Task(async () => int1 = await LongTask1(); ));
。这种方法的一个问题是编译器将无法识别该变量已被分配,并且如果您不给它一个初始值,它将把它视为未分配。【参考方案5】:
回答这个问题:
我希望 Sleep 是一个异步方法,以便它可以等待其他方法
您可以像这样重写Sleep
函数:
private static async Task<int> Sleep(int ms)
Console.WriteLine("Sleeping for " + ms);
var task = Task.Run(() => Thread.Sleep(ms));
await task;
Console.WriteLine("Sleeping for " + ms + "END");
return ms;
static void Main(string[] args)
Console.WriteLine("Starting");
var task1 = Sleep(2000);
var task2 = Sleep(1000);
int totalSlept = task1.Result +task2.Result;
Console.WriteLine("Slept for " + totalSlept + " ms");
Console.ReadKey();
运行此代码将输出:
Starting
Sleeping for 2000
Sleeping for 1000
*(one second later)*
Sleeping for 1000END
*(one second later)*
Sleeping for 2000END
Slept for 3000 ms
【讨论】:
【参考方案6】:现在是周末!
public async void Go()
Console.WriteLine("Start fosterage...");
var t1 = Sleep(5000, "Kevin");
var t2 = Sleep(3000, "Jerry");
var result = await Task.WhenAll(t1, t2);
Console.WriteLine($"My precious spare time last for only result.Max()ms");
Console.WriteLine("Press any key and take same beer...");
Console.ReadKey();
private static async Task<int> Sleep(int ms, string name)
Console.WriteLine($"name going to sleep for msms :)");
await Task.Delay(ms);
Console.WriteLine("$name waked up after msms :(";
return ms;
【讨论】:
以上是关于并行运行两个异步任务并在 .NET 4.5 中收集结果的主要内容,如果未能解决你的问题,请参考以下文章
与 MVC 3、.NET 4.5 和 EF 6 并行获取数据