首页 > 解决方案 > 在 C#.NET 中将任意数量的任务链接在一起

问题描述

我有的

我有一套异步处理方法,类似于:

public class AsyncProcessor<T>
{
    //...rest of members, etc.

    public Task Process(T input)
    {
        //Some special processing, most likely inside a Task, so
        //maybe spawn a new Task, etc.
        Task task = Task.Run(/* maybe private method that does the processing*/);
        return task;
    }
}

我想要的是

我想将它们全部链接在一起,按顺序执行。

我试过的

我试图做以下事情:

public class CompositeAsyncProcessor<T>
{
    private readonly IEnumerable<AsyncProcessor<T>> m_processors;

    //Constructor receives the IEnumerable<AsyncProcessor<T>> and
    //stores it in the field above.

    public Task ProcessInput(T input)
    {
        Task chainedTask = Task.CompletedTask;

        foreach (AsyncProcessor<T> processor in m_processors)
        {
            chainedTask = chainedTask.ContinueWith(t => processor.Process(input));
        }

        return chainedTask;
    }
}

什么地方出了错

但是,任务不会按顺序运行,因为据我所知,在对 的调用中ContinueWithprocessor.Process(input)调用会立即执行,并且该方法会独立于返回的任务的状态返回。因此,所有处理任务仍然几乎同时开始。

我的问题

我的问题是我是否可以做一些优雅的事情来按顺序链接任务(即没有执行重叠)。例如,我可以使用以下语句来实现这一点(我在细节上有点挣扎)吗?

chainedTask = chainedTask.ContinueWith(async t => await processor.Process(input));

另外,如果不使用 async/await,我将如何做到这一点ContinueWith

我为什么要这样做?

因为我的Processor对象可以访问和请求“线程不安全”资源。另外,我不能只是等待所有方法,因为我不知道它们有多少,所以我不能只写下必要的代码行。

线程不安全是什么意思?一个具体的问题

因为我可能错误地使用了这个术语,所以用一个插图来解释这一点会更好一些。在我的Processor对象使用的“资源”中,它们都可以访问如下对象:

public interface IRepository
{
    void Add(object obj);

    bool Remove(object obj);

    IEnumerable<object> Items { get; }
}

目前使用的实现比较幼稚。因此,有些Processor对象添加了东西,而另一些对象则检索了Items以供检查。当然,我经常遇到的例外之一是:

InvalidOperationException: 集合被修改,枚举操作可能无法执行。

我可以花一些时间锁定访问和预运行枚举。然而,这是我要考虑的第二个选项,而我的第一个想法是让进程按顺序运行。

为什么我必须使用任务?

虽然在这种情况下我可以完全控制,但我可以说,出于问题的目的,我可能无法更改基本实现,那么如果我被任务卡住会发生什么?此外,这些操作实际上确实代表了相对耗时的 CPU 密集型操作,而且我正在尝试实现响应式用户界面,因此我需要减轻异步操作的一些负担。虽然很有用,并且在我的大多数用例中,没有必要链接多个,而是每次链接一个(或几个,但总是特定的和特定的计数,所以我能够挂钩它们一起没有迭代和异步/等待),其中一个用例最终需要将未知数量的任务链接在一起。

我目前如何处理

我目前处理这个问题的方式是将调用附加到调用Wait()内部ContinueWith,即:

foreach (AsyncProcessor<T> processor in m_processors)
{
    chainedTask = chainedTask.ContinueWith(t => processor.Process(input).Wait());
}

我将不胜感激任何关于我应该如何做到这一点的想法,或者我如何能更优雅地做到这一点(或者,可以说是“异步正确”)。另外,我想知道如何在没有 async/await的情况下做到这一点。

为什么我的问题与这个问题不同,后者并没有完全回答我的问题。

因为链接的问题有两个任务,所以解决方案是简单地编写所需的行,而我有任意(和未知)数量的任务,所以我需要一个合适的迭代。另外,我的方法不是 async。我现在明白(从已删除的单个简要可用答案中)如果我将方法更改为asyncawait每个处理器的Task方法,我可以相当容易地做到这一点,但我仍然想知道如何在没有 async/await 语法的情况下实现这一点。

为什么我的问题不是其他链接问题的重复

因为他们都没有解释如何正确使用链接ContinueWith,而且我对使用ContinueWith不使用 async/await 模式的解决方案感兴趣。我知道这种模式可能是更好的解决方案,我想了解如何(如果可能)ContinueWith正确使用调用进行任意链接。我现在知道我不需要ContinueWith。问题是,我该怎么做ContinueWith

标签: c#asynchronoustasktask-parallel-librarymethod-chaining

解决方案


foreach+将按顺序await运行es。Process

    public async Task ProcessInputAsync(T input)
    {
        foreach (var processor in m_processors)
        {
            await processor.Process(input));
        }
    }

顺便提一句。Process, 应该称为ProcessAsync


推荐阅读