并行运行两个异步任务并在 .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) 在完成之前不会运行。这是真的,即使 Sleepasync 并且我没有使用 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&lt;TResult&gt; t1 = LongTask1();,现在我得到了t1.Result&lt;TResult&gt; 是结果的返回类型。您需要在您的方法中使用 return &lt;TResult&gt; 才能使其正常工作。 值得一提的是,如果您正在做一些非常简单的事情并且不想要额外的t1t2 变量,您可以使用new Task(...)。例如:int int1 = 0; await Task.WhenAll(new Task(async () =&gt; 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 中收集结果的主要内容,如果未能解决你的问题,请参考以下文章

Windows窗体中的C#4.5任务并行[重复]

与 MVC 3、.NET 4.5 和 EF 6 并行获取数据

主任务并行运行,子任务依次运行。

在 iOS swift 中异步/并发/并行运行任务

如何正确理解.NET 4.5和C#5.0中的async/await异步编程模式

.Net 4.5 中的异步 HttpClient 对于密集负载应用程序来说是一个糟糕的选择吗?