同时执行多个长 运行 进程的最简单方法 c#

Simplest way to perform multiple long running processes concurrently c#

我有一份很长的 运行ning 工作,我需要为集合中的每个项目 运行 一次。

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

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

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);
            });
}

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

其实你的第一个假设

If I understand it right though, THIS does each task one at a time still

是错误的,很容易证明:

    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)'

时启动

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

这是因为方法returns 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 是异步实现的。