简体   繁体   English

IAsyncEnumerable 类似于 akka 流的源

[英]IAsyncEnumerable like a Source for akka streams

I want to use IAsyncEnumerable like a Source for akka streams.我想将 IAsyncEnumerable 用作 akka 流的 Source。 But I not found, how do it.但是我没找到,怎么办。

No sutiable method in Source class for this code.此代码的 Source 类中没有合适的方法。

using System.Collections.Generic;
using System.Threading.Tasks;
using Akka.Streams.Dsl;

namespace ConsoleApp1
{
    class Program
    {
        static async Task Main(string[] args)
        { 
            Source.From(await AsyncEnumerable())
                .Via(/*some action*/)
                //.....
        }

        private static async IAsyncEnumerable<int> AsyncEnumerable()
        {
            //some async enumerable
        }
    }
}

How use IAsyncEnumerbale for Source?如何将 IAsyncEnumerbale 用于 Source?

This has been done in the past as a part of Akka.NET Streams contrib package, but since I don't see it there anymore, let's go through on how to implement such source.这在过去是作为 Akka.NET Streams contrib 包的一部分完成的,但由于我不再在那里看到它,让我们来看看如何实现这样的源代码。 The topic can be quite long, as:主题可能很长,例如:

  1. Akka.NET Streams is really about graph processing - we're talking about many-inputs/many-outputs configurations (in Akka.NET they're called inlets and outlets) with support for cycles in graphs. Akka.NET Streams 实际上是关于图形处理的——我们谈论的是多输入/多输出配置(在 Akka.NET 中,它们被称为入口和出口)并支持图形中的循环。
  2. Akka.NET is not build on top of .NET async/await or even on top of .NET standard thread pool library - they're both pluggable, which means that the lowest barier is basically using callbacks and encoding what C# compiler sometimes does for us. Akka.NET 不是建立在 .NET async/await 之上,甚至不是建立在 .NET 标准线程池库之上——它们都是可插入的,这意味着最低的障碍基本上是使用回调和编码 C# 编译器有时会为我们。
  3. Akka.NET streams is capable of both pushing and pulling values between stages/operators. Akka.NET 流能够在阶段/操作符之间推送和拉取值。 IAsyncEnumerable<T> can only pull data while IObservable<T> can only push it, so we get more expressive power here, but this comes at a cost. IAsyncEnumerable<T>只能拉取数据,而IObservable<T>只能推送数据,所以我们在这里获得了更多的表达能力,但这IAsyncEnumerable<T>

The basics of low level API used to implement custom stages can be found in the docs .用于实现自定义阶段的低级 API 的基础知识可以在文档中找到。

The starter boilerplate looks like this:启动样板如下所示:

public static class AsyncEnumerableExtensions {
    // Helper method to change IAsyncEnumerable into Akka.NET Source.
    public static Source<T, NotUsed> AsSource<T>(this IAsyncEnumerable<T> source) => 
        Source.FromGraph(new AsyncEnumerableSource<T>(source));
}

// Source stage is description of a part of the graph that doesn't consume
// any data, only produce it using a single output channel.
public sealed class AsyncEnumerableSource<T> : GraphStage<SourceShape<T>>
{
    private readonly IAsyncEnumerable<T> _enumerable;

    public AsyncEnumerableSource(IAsyncEnumerable<T> enumerable)
    {
        _enumerable = enumerable;
        Outlet = new Outlet<T>("asyncenumerable.out");
        Shape = new SourceShape<T>(Outlet);
    }

    public Outlet<T> Outlet { get; }
    public override SourceShape<T> Shape { get; }

    /// Logic if to a graph stage, what enumerator is to enumerable.
    protected override GraphStageLogic CreateLogic(Attributes inheritedAttributes) => 
        new Logic(this);

    sealed class Logic: OutGraphStageLogic
    {
        public override void OnPull()
        {
            // method called whenever a consumer asks for new data
        }

        public override void OnDownstreamFinish() 
        {
            // method called whenever a consumer stage finishes,used for disposals
        }
    }
}

As mentioned, we don't use async/await straight away here: even more, calling Logic methods in asynchronous context is unsafe.如前所述,我们不会在这里直接使用 async/await:更重要的是,在异步上下文中调用Logic方法是不安全的。 To make it safe we need to register out methods that may be called from other threads using GetAsyncCallback<T> and call them via returned wrappers.为了确保安全,我们需要使用GetAsyncCallback<T>注册可能从其他线程调用的方法,并通过返回的包装器调用它们。 This will ensure, that not data races will happen when executing asynchronous code.这将确保在执行异步代码时不会发生数据竞争。

sealed class Logic : OutGraphStageLogic
{
    private readonly Outlet<T> _outlet;
    // enumerator we'll call for MoveNextAsync, and eventually dispose
    private readonly IAsyncEnumerator<T> _enumerator;
    // callback called whenever _enumerator.MoveNextAsync completes asynchronously
    private readonly Action<Task<bool>> _onMoveNext;
    // callback called whenever _enumerator.DisposeAsync completes asynchronously
    private readonly Action<Task> _onDisposed;
    // cache used for errors thrown by _enumerator.MoveNextAsync, that
    // should be rethrown after _enumerator.DisposeAsync
    private Exception? _failReason = null;
    
    public Logic(AsyncEnumerableSource<T> source) : base(source.Shape)
    {
        _outlet = source.Outlet;
        _enumerator = source._enumerable.GetAsyncEnumerator();
        _onMoveNext = GetAsyncCallback<Task<bool>>(OnMoveNext);
        _onDisposed = GetAsyncCallback<Task>(OnDisposed);
    }

    // ... other methods
}

The last part to do are methods overriden on `Logic:最后要做的部分是覆盖在`Logic 上的方法:

  • OnPull used whenever the downstream stage calls for new data.每当下游阶段调用新数据时使用OnPull Here we need to call for next element of async enumerator sequence.这里我们需要调用异步枚举器序列的下一个元素。
  • OnDownstreamFinish called whenever the downstream stage has finished and will not ask for any new data. OnDownstreamFinish在下游阶段完成时调用,并且不会要求任何新数据。 It's the place for us to dispose our enumerator.这是我们处理枚举器的地方。

Thing is these methods are not async/await, while their enumerator's equivalent are.问题是这些方法不是 async/await,而它们的枚举器等价物是。 What we basically need to do there is to:我们基本上需要做的是:

  1. Call corresponding async methods of underlying enumerator ( OnPullMoveNextAsync and OnDownstreamFinishDisposeAsync ).调用底层枚举器的相应异步方法( OnPullMoveNextAsyncOnDownstreamFinishDisposeAsync )。
  2. See, if we can take their results immediately - it's important part that usually is done for us as part of C# compiler in async/await calls.看,如果我们可以立即获取他们的结果 - 这是一个重要的部分,通常作为 async/await 调用中的 C# 编译器的一部分为我们完成。
  3. If not, and we need to wait for the results - call ContinueWith to register our callback wrappers to be called once async methods are done.如果没有,我们需要等待结果 - 调用ContinueWith来注册我们的回调包装器,以便在异步方法完成后调用。
sealed class Logic : OutGraphStageLogic
{
    // ... constructor and fields

    public override void OnPull()
    {
        var hasNext = _enumerator.MoveNextAsync();
        if (hasNext.IsCompletedSuccessfully)
        {
            // first try short-path: values is returned immediately
            if (hasNext.Result)
                // check if there was next value and push it downstream
                Push(_outlet, _enumerator.Current);
            else
                // if there was none, we reached end of async enumerable
                // and we can dispose it
                DisposeAndComplete();
        }
        else
            // we need to wait for the result
            hasNext.AsTask().ContinueWith(_onMoveNext);
    }

    // This method is called when another stage downstream has been completed
    public override void OnDownstreamFinish() =>
        // dispose enumerator on downstream finish
        DisposeAndComplete();
    
    private void DisposeAndComplete()
    {
        var disposed = _enumerator.DisposeAsync();
        if (disposed.IsCompletedSuccessfully)
        {
            // enumerator disposal completed immediately
            if (_failReason is not null)
                // if we close this stream in result of error in MoveNextAsync,
                // fail the stage
                FailStage(_failReason);
            else
                // we can close the stage with no issues
                CompleteStage();
        }
        else 
            // we need to await for enumerator to be disposed
            disposed.AsTask().ContinueWith(_onDisposed);
    }

    private void OnMoveNext(Task<bool> task)
    {
        // since this is callback, it will always be completed, we just need
        // to check for exceptions
        if (task.IsCompletedSuccessfully)
        {
            if (task.Result)
                // if task returns true, it means we read a value
                Push(_outlet, _enumerator.Current);
            else
                // otherwise there are no more values to read and we can close the source
                DisposeAndComplete();
        }
        else
        {
            // task either failed or has been cancelled
            _failReason = task.Exception as Exception ?? new TaskCanceledException(task);
            FailStage(_failReason);
        }
    }

    private void OnDisposed(Task task)
    {
        if (task.IsCompletedSuccessfully) CompleteStage();
        else {
            var reason = task.Exception as Exception 
                ?? _failReason 
                ?? new TaskCanceledException(task);
            FailStage(reason);
        }
    }
}

As of Akka.NET v1.4.30 this is now natively supported inside Akka.Streams via the RunAsAsyncEnumerable method:Akka.NET v1.4.30 开始,现在通过RunAsAsyncEnumerable方法在RunAsAsyncEnumerable内部支持RunAsAsyncEnumerable

var input = Enumerable.Range(1, 6).ToList();

var cts = new CancellationTokenSource();
var token = cts.Token;

var asyncEnumerable = Source.From(input).RunAsAsyncEnumerable(Materializer);
var output = input.ToArray();
bool caught = false;
try
{
    await foreach (var a in asyncEnumerable.WithCancellation(token))
    {
        cts.Cancel();
    }
}
catch (OperationCanceledException e)
{
    caught = true;
}

caught.ShouldBeTrue();

I copied that sample from the Akka.NET test suite, in case you're wondering.如果您想知道,我从 Akka.NET 测试套件复制了该示例。

You can also use an existing primitive for streaming large collections of data.您还可以使用现有的原语来流式传输大量数据。 Here is an example of using Source.unfoldAsync to stream pages of data - in this case github repositories using Octokit - until there is no more.这是一个使用 Source.unfoldAsync 流式传输数据页面的示例 - 在本例中是使用 Octokit 的 github 存储库 - 直到没有更多数据。

var source = Source.UnfoldAsync<int, RepositoryPage>(startPage, page =>
{
    var pageTask = client.GetRepositoriesAsync(page, pageSize);    
    var next = pageTask.ContinueWith(task =>
    {
        var page = task.Result;
        if (page.PageNumber * pageSize > page.Total) return Option<(int, RepositoryPage)>.None;
        else return new Option<(int, RepositoryPage)>((page.PageNumber + 1, page));
    });

    return next;
}); 

To run跑步

using var sys = ActorSystem.Create("system");
using var mat = sys.Materializer();

int startPage = 1;
int pageSize = 50;

var client = new GitHubClient(new ProductHeaderValue("github-search-app"));

var source = ...

var sink = Sink.ForEach<RepositoryPage>(Console.WriteLine);

var result = source.RunWith(sink, mat);
await result.ContinueWith(_ => sys.Terminate());
class Page<T>
{
    public Page(IReadOnlyList<T> contents, int page, long total)
    {        
        Contents = contents;
        PageNumber = page;
        Total = total;
    }
    
    public IReadOnlyList<T> Contents { get; set; } = new List<T>();
    public int PageNumber { get; set; }
    public long Total { get; set; }
}

class RepositoryPage : Page<Repository>
{
    public RepositoryPage(IReadOnlyList<Repository> contents, int page, long total) 
        : base(contents, page, total)
    {
    }

    public override string ToString() => 
        $"Page {PageNumber}\n{string.Join("", Contents.Select(x => x.Name + "\n"))}";
}

static class GitHubClientExtensions
{
    public static async Task<RepositoryPage> GetRepositoriesAsync(this GitHubClient client, int page, int size)
    {
        // specify a search term here
        var request = new SearchRepositoriesRequest("bootstrap")
        {
            Page = page,
            PerPage = size
        };

        var result = await client.Search.SearchRepo(request);
        return new RepositoryPage(result.Items, page, result.TotalCount);        
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 合并多个 IAsyncEnumerable 流 - Merge multiple IAsyncEnumerable streams 从多个 IAsyncEnumerable 流并行接收数据 - Parallel receiving data from several IAsyncEnumerable streams 如何实现一个高效的 WhenEach 流式传输任务结果的 IAsyncEnumerable? - How to implement an efficient WhenEach that streams an IAsyncEnumerable of task results? 是否有类似实现 IAsyncEnumerable 的队列的 C# class? - Is there a C# class like Queue that implements IAsyncEnumerable? ToArrayAsync() 抛出“源 IQueryable 未实现 IAsyncEnumerable” - ToArrayAsync() throws "The source IQueryable doesn't implement IAsyncEnumerable" Akka.Net:网络上的反应式流 - Akka.Net: Reactive Streams over the network .Net Core 单元测试错误 - 源 IQueryable 没有实现 IAsyncEnumerable&lt;…&gt; - .Net Core Unit Test Error - The source IQueryable doesn't implement IAsyncEnumerable<…> MSTest v2 和源 IQueryable 未使用 Entity Framework Core 实现 IAsyncEnumerable - MSTest v2 and The source IQueryable doesn't implement IAsyncEnumerable with Entity Framework Core 获取 mongodb 文档但 Iqueriable 上的 ToListAsync 引发异常源 IQueryable 未实现 IAsyncEnumerable - Getting mongodb documents but ToListAsync on Iqueriable throws exception The source IQueryable doesn't implement IAsyncEnumerable Akka.Streams.Kafka - 无法加载消费者 - Akka.Streams.Kafka - can't load a consumer
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM