【问题标题】:Chaining async/await链接异步/等待
【发布时间】:2020-01-05 05:24:27
【问题描述】:

我开发了许多算法,它们使用常规的Threads 自行完成大部分线程。方法总是如下

float[] GetData(int requestedItemIndex)

通过上述方法,索引被推入一些消息队列,由个别算法的线程处理。所以最终算法的界面是这样的:

public abstract class AlgorithmBase
{
    private readonly AlgorithmBase Parent;

    private void RequestQueue()
    {
    }

    public float[] GetData(int requestedItemIndex) => Parent.GetData(requestedItemIndex);
}

这个例子非常原始,但只是为了获得线索。问题是我可以链接当前适用于我的解决方案的算法。如您所见,每个 GetData 都会调用父算法的另一个 GetData。这当然会变得更复杂,当然需要有一个最终父级作为数据源,否则我会得到 StackOverflowExceptions。

现在我尝试通过使用 async/await 来改变这种行为。我的问题是,如果我重写我的代码,我会得到这样的结果:

  public abstract class AlgorithmBase
    {
        private readonly AlgorithmBase Parent;


        public async Task<float[]> GetDataAsync(int requestedItemIndex, CancellationToken token = default)
        {
            var data = await Parent.GetDataAsync(requestedItemIndex);

            return await Task.Run<float[]>(async () => ProcessData());
        }            
    }

现在,我已经链接了算法,每个新算法都跨越另一个任务,如果多次执行,这可能会非常耗时。

所以我的问题是,是否有一种方法可以通过使用定义接口将下一个任务嵌入到已经运行的任务中?

【问题讨论】:

  • 问题在于任务,任务调度程序可能比您在 cpu 绑定和 io 绑定的工作负载中更好地管理它们。我不确定问题到底出在哪里。如果您想管道数据和工作流,最好查看 DataFlow 或 RX
  • @TheGeneral:我查看了这两个解决方案(DataFlow 和 Rx)。我的问题是我的所有需求都是基于拉的而不是基于推的。
  • @GSerg:感谢您的评论。老实说,我没有测试代码,我只是插入它以进行解释。
  • @GSerg 假设ProcessData是同步方法,示例代码会产生警告(因为有一个async没有await),但是会等待方法的结果正好。 Task.Run() 理解异步委托。
  • @TheodorZoulias 出于某种原因,它在我脑海中闪过stackoverflow.com/q/24777253/11683

标签: c# async-await task-parallel-library


【解决方案1】:

无需显式使用Task.Run。您应该避免这种情况,并将选择权留给AlgorithmBase 类的消费者。

因此,您可以非常相似地实现async 版本,其中Task 对象将从父母传播到孩子:

public abstract class AlgorithmBase
{
    private readonly AlgorithmBase Parent;

    private void RequestQueue()
    {
    }

    public Task<float[]> GetDataAsync(int requestedItemIndex) 
         => Parent.GetDataAsync(requestedItemIndex);
}

最终,一些“父”将实现GetDataAsync,与同步对应物的方式相同。

public class SortAlgorithm : AlgorithmBase
{
    public override async Task<float[]> GetDataAsync(int requestedItemIndex)
    {
        // asynchronously get data 
        var data = await Parent.GetDataAsync(requestedItemIndex);

        // synchronously process data and return from asynchronous method
        return this.ProcessData(data);
    }

    private float[] ProcessData(float[] data)
    {
    }
}

最后,SortAlogirthm 的消费者可以决定是 await 它,还是一劳永逸。

var algo = new SortAlgorithm();

// asynchronously wait until it's finished
var data = await algo.GetDataAsync(1);

// start processing without waiting for the result
algo.GetDataAsync(1);

// not needed - GetDataAsync already returns Task, Task.Run is not needed in this case
Task.Run(() => algo.GetDataAsync(1));

【讨论】:

  • 尽量不要考虑线程,而是考虑任务。每个任务都将在从 ThreadPool 中获取的可用线程上调用,因此后面的线程由框架管理。
  • 如果 ProcessData() 受 CPU 限制,那么您必须在某处使用 Task.Run()。
【解决方案2】:

在库代码中等待时,您通常希望每次都avoid capturing and restoring the context,尤其是当您在循环中等待时。因此,要提高库的性能,请考虑在所有 awaits 上使用 .ConfigureAwait(false)

【讨论】:

    猜你喜欢
    • 2013-02-26
    • 2020-10-07
    • 1970-01-01
    • 2021-01-24
    • 1970-01-01
    • 2021-11-09
    • 2023-03-12
    • 2016-07-07
    • 2016-03-25
    相关资源
    最近更新 更多