【问题标题】:Simplest way to perform multiple long running processes concurrently c#同时执行多个长时间运行的进程的最简单方法c#
【发布时间】:2020-10-24 12:36:47
【问题描述】:

我有一个长时间运行的作业,我需要为集合中的每个项目运行一次。

我想同时完成这些工作,尽管整个程序可以等待所有工作完成。 (我以后可能会改变它,但现在,我让问题保持简单)

根据已经给出的一些帮助,我得到了以下模式:

public static void DoWork()
{
    //Get a collection of items
    var items = GetMyItems();

}


private async void DoStuffWithItems(ICollection<MyItem> items)
{
    var tasks = items.Select (i => DoStuffWithItem(i));
    await Task.WhenAll(tasks);
}

private Task DoStuffWithItem(MyItem item)
{
    //LongRunningTask
    return Task.Run(async () =>
            {
                var returnObject = await LongRunningAsyncMethod(item);
            });
}

如果我理解正确的话,这仍然一次只完成一项任务 - 太没有意义了。

有人建议我将 Parallel.ForEach 与 async await 结合使用 - Parallel-ForEach 模式很简单:

public static void DoWork()
{
    //Get a collection of items
    var items = GetMyItems();
    
    Parallel.ForEach(items, (item) =>
    {
        var returnObject = LongRunningMethod(item);
    }

}

现在,如果我理解正确,我将为每个项目启动一个单独的线程来执行它的操作,但是在执行此操作时,主线程将等待

但是如果我希望程序的其余部分在 DoWork 等待 Parallel.ForEach 完成时继续执行呢?

public static void DoWork()
{

//Get a collection of items
var items = GetMyItems();

DoINeedThisMethod(items);


}

private async void DoINeedThisMethod(ICollection<MyItem> items)
    {
        await Task.Run(() => {
            DoStuffWithItems(items);
        });
    }

private async void DoStuffWithItems(ICollection<MyItem> items)
{
        var newobjs = new List<MyItem>();

            Parallel.ForEach(items, (item) =>
            {
                var bc = LongRunningAsyncMethod(item);
            });
}

感觉不太对劲——而且我那里有一个异步 void,这不是一个好习惯。但我在这里是正确的吗? 如果我理解正确,在 DoINeedThisMethod 中,我正在启动一个新线程(通过任务)来执行 Parallel.ForEach 调用,但是在那里使用“等待”意味着主线程现在将继续,而 Parallel.ForEach完成。

【问题讨论】:

    标签: c# multithreading async-await parallel.foreach


    【解决方案1】:

    其实,你的第一个假设

    如果我理解正确的话,THIS 会一次完成一项任务 还是

    很容易证明是错误的:

        private static async Task Main(string[] args)
        {
    
            async Task DoStuffWithItem(int i)
            {
                // long running task
                Console.WriteLine($"processing item {i} started");
                await Task.Delay(500);
                Console.WriteLine($"processing item {i} finished");
            }
            
            var items = new List<int> { 1, 2, 3 };
    
            var tasks = items.Select(i => DoStuffWithItem(i)).ToList();
            await Task.WhenAll(tasks);
    
            Console.WriteLine("\nFinished");
        }
    

    产生以下输出:

    processing item 1 started
    processing item 2 started
    processing item 3 started
    processing item 2 finished
    processing item 1 finished
    processing item 3 finished
    
    Finished
    

    所以任务是并发执行的……

    注意:正如下面评论中所解释的,“ToList()”会导致 Linq 查询立即执行(立即启动任务)。 如果没有 'ToList()',任务只会在执行 'await Task.WhenAll(tasks)' 时启动

    【讨论】:

    • 叹息 - 我今天开始时是如此接近 - 但随后又掉进了一个长长的兔子洞 :( 在我的第三点上,大概这会在这些任务完成时阻止主线程。如果而不是 Main() 它是在私有静态异步 Task MyBackgroundWorker() 中完成的,我只是从 GUI 调用该方法,在其中使用 Task.WhenAll(tasks) 是否意味着 GUI 没有等待所有任务完成?
    • @tonydev314:我感觉你还没有正确理解“Task.Wait”和“await”之间的区别:如果你等待任务的执行,你的代码会停在那一点(直到任务停止运行)。但是,运行代码的线程不会被阻塞。在您的示例中,GUI 线程仍会对用户和系统事件做出反应。 Async..Await 的一个很好的介绍:blog.stephencleary.com/2012/02/async-and-await.html
    • 还有一点需要注意:在示例中,您使用 Linq(“选择”方法)构建“任务”实例。这有一个副作用:在评估“任务”之前,不会执行 Linq 枚举。因此,在上面的示例中,长时间运行的任务在您到达“await Task.WhenAll(tasks)”语句之前不会启动。如果要启动任务,请在等待任务完成之前执行更多操作,但是(在它们运行时),您需要强制执行 Linq 查询(例如,通过调用 'ToList': var tasks = items.Select (i => DoStuffWithItem(i)).ToList();'
    【解决方案2】:

    正如@Johan 所证实的,对DoStuffWithItem 的调用应该同时发生。

    这是因为该方法返回Task,如果省略lambda表达式可能会更清楚,这完全没有必要:

    var tasks = items.Select(DoStuffWithItem);
    

    另外,您不应该在DoStuffWithItem 的实现中使用Task.Run。如果LongRunningAsyncMethod 是真正异步的,它会在I/O 发生时释放线程,这反过来又允许您的Select 并行生成任务。

    使用Task.Run 只是通过线程切换和分配增加了不必要的开销。

    你应该放弃将Parallel.ForEachawait结合的想法;它们有不同的用途。

    Parallel 使用多个线程,适合 CPU 密集型工作,而async 在异步工作 (I/O) 发生时释放线程。

    您应该坚持使用 Select / WhenAll 版本,但不要使用 Task.Run,只要 LongRunningAsyncMethod 是异步实现的。

    【讨论】:

    • 是的 - 完成了 - 调用的方法被标记为异步,但它有一个问题。似乎 Await.When 在调用方法中,不会等待长时间运行的任务中的等待完成 - 这是在 Test 过程的范围内。因此测试完成,最终断言失败。我假设等待冒泡?即等待调用堆栈中的所有等待?
    • 好吧,我的错 - 我在调用方法中使用了 await,如果该方法是异步的,那很好,但在测试中,我需要删除那里的 await。
    • @tonydev314 为什么会这样?现代 UT 框架将始终支持异步测试方法。
    猜你喜欢
    • 2022-07-29
    • 1970-01-01
    • 1970-01-01
    • 2021-10-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-27
    • 1970-01-01
    相关资源
    最近更新 更多