Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/.net/21.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 异步处理大量数据流_C#_.net_Task Parallel Library_Async Await - Fatal编程技术网

C# 异步处理大量数据流

C# 异步处理大量数据流,c#,.net,task-parallel-library,async-await,C#,.net,Task Parallel Library,Async Await,我们使用IEnumerables从数据库返回大量数据集: public IEnumerable<Data> Read(...) { using(var connection = new SqlConnection(...)) { // ... while(reader.Read()) { // ... yield return item; } } }

我们使用IEnumerables从数据库返回大量数据集:

public IEnumerable<Data> Read(...)
{
    using(var connection = new SqlConnection(...))
    {
        // ...
        while(reader.Read())
        {
            // ...
            yield return item;
        }
    }
}
public IEnumerable Read(…)
{
使用(var connection=newsqlconnection(…)
{
// ...
while(reader.Read())
{
// ...
收益回报项目;
}
}
}
现在我们想使用异步方法来做同样的事情。但是,异步没有IEnumerables,因此我们必须将数据收集到一个列表中,直到加载整个数据集:

public async Task<List<Data>> ReadAsync(...)
{
    var result = new List<Data>();
    using(var connection = new SqlConnection(...))
    {
        // ...
        while(await reader.ReadAsync().ConfigureAwait(false))
        {
            // ...
            result.Add(item);
        }
    }
    return result;
}
公共异步任务ReadAsync(…) { var result=新列表(); 使用(var connection=newsqlconnection(…) { // ... while(wait reader.ReadAsync().configurewait(false)) { // ... 结果.添加(项目); } } 返回结果; }
这将消耗服务器上的大量资源,因为在返回之前,所有数据都必须在列表中。IEnumerables处理大型数据流的最佳且易于使用的异步替代方案是什么?我希望避免在处理时将所有数据存储在内存中。

最简单的选择是使用。您所需要做的就是配置一个用于处理处理(如果您愿意,可以并行)的,并以异步方式将项目逐个“发送”到其中。
我还建议设置一个
BoundedCapacity
,当处理无法处理速度时,它将限制读卡器从数据库中读取数据

var block = new ActionBlock<Data>(
    data => ProcessDataAsync(data),
    new ExecutionDataflowBlockOptions
    {
        BoundedCapacity = 1000,
        MaxDegreeOfParallelism = Environment.ProcessorCount
    });

using(var connection = new SqlConnection(...))
{
    // ...
    while(await reader.ReadAsync().ConfigureAwait(false))
    {
        // ...
       await block.SendAsync(item);
    }
}
var block=新动作块(
数据=>ProcessDataAsync(数据),
新的ExecutionDataflowBlockOptions
{
边界容量=1000,
MaxDegreeOfParallelism=Environment.ProcessorCount
});
使用(var connection=newsqlconnection(…)
{
// ...
while(wait reader.ReadAsync().configurewait(false))
{
// ...
等待block.SendAsync(项);
}
}
您也可以使用,但这是一个比您可能需要的更复杂、更健壮的框架

这将消耗服务器上的大量资源,因为 返回前,数据必须在列表中。什么是最好的和容易的 使用IEnumerables的异步替代方法处理大数据 溪流?我希望避免在运行时将所有数据存储在内存中 处理

如果您不想立即将所有数据发送到客户端,您可以考虑使用(在客户端上)和(在客户端和服务器上)来处理这一点。

signar
将允许异步向客户端发送数据
Rx
允许在数据项到达客户端时将LINQ应用于数据项的异步序列。然而,这将改变客户机-服务器应用程序的整个代码模型

示例(Samuel Jack的博客文章):

相关问题(如果不是副本):


正如其他一些海报所提到的,这可以通过Rx实现。使用Rx,该功能将返回一个可订阅的
IObservable
,并在数据可用时将数据推送到订户
IObservable
还支持LINQ,并添加了一些自己的扩展方法

更新

我添加了两个通用帮助器方法,以使读取器的使用可重用,并支持取消

public static class ObservableEx
    {
        public static IObservable<T> CreateFromSqlCommand<T>(string connectionString, string command, Func<SqlDataReader, Task<T>> readDataFunc)
        {
            return CreateFromSqlCommand(connectionString, command, readDataFunc, CancellationToken.None);
        }

        public static IObservable<T> CreateFromSqlCommand<T>(string connectionString, string command, Func<SqlDataReader, Task<T>> readDataFunc, CancellationToken cancellationToken)
        {
            return Observable.Create<T>(
                async o =>
                {
                    SqlDataReader reader = null;

                    try
                    {                        
                        using (var conn = new SqlConnection(connectionString))
                        using (var cmd = new SqlCommand(command, conn))
                        {
                            await conn.OpenAsync(cancellationToken);
                            reader = await cmd.ExecuteReaderAsync(CommandBehavior.CloseConnection, cancellationToken);

                            while (await reader.ReadAsync(cancellationToken))
                            {
                                var data = await readDataFunc(reader);
                                o.OnNext(data);                                
                            }

                            o.OnCompleted();
                        }
                    }
                    catch (Exception ex)
                    {
                        o.OnError(ex);
                    }

                    return reader;
                });
        }
    }
用法

您可以通过提供一个
IObserver
来订阅Observable,但也存在占用lambda的重载。当数据可用时,调用
OnNext
回调。如果存在异常,则调用
OnError
回调。最后,如果没有更多数据,则调用
OnCompleted
回调

如果您想取消可观察的,只需处理订阅即可

void Main()
{
   // This is an asyncrhonous call, it returns straight away
    var subscription = ReadData()
        .Skip(5)                        // Skip first 5 entries, supports LINQ               
        .Delay(TimeSpan.FromSeconds(1)) // Rx operator to delay sequence 1 second
        .Subscribe(x =>
    {
        // Callback when a new Data is read
        // do something with x of type Data
    },
    e =>
    {
        // Optional callback for when an error occurs
    },
    () =>
    {
        //Optional callback for when the sequenc is complete
    }
    );

    // Dispose subscription when finished
    subscription.Dispose();

    Console.ReadKey();
}

大多数情况下,在处理异步/等待方法时,我发现更容易扭转问题,使用函数
(Func
)或操作(
Action
)而不是特别的代码,尤其是
IEnumerable
yield

换句话说,当我想到“异步”时,我试图忘记函数“返回值”的旧概念,这一概念在其他方面非常明显,而且我们非常熟悉

例如,如果您将初始同步代码更改为以下代码(
处理器
是最终将对一个数据项执行操作的代码):

公共无效读取(…,操作处理器)
{
使用(var connection=newsqlconnection(…)
{
// ...
while(reader.Read())
{
// ...
处理器(项目);
}
}
}
然后,异步版本非常容易编写:

public async Task ReadAsync(..., Action<Data> processor)
{
    using(var connection = new SqlConnection(...))
    {
        // note you can use connection.OpenAsync()
        // and command.ExecuteReaderAsync() here
        while(await reader.ReadAsync())
        {
            // ...
            processor(item);
        }
    }
}
公共异步任务ReadAsync(…,操作处理器)
{
使用(var connection=newsqlconnection(…)
{
//注意:您可以使用connection.OpenAsync()
//和command.ExecuteReaderAsync()在此处
while(等待reader.ReadAsync())
{
// ...
处理器(项目);
}
}
}

如果可以这样更改代码,则不需要任何扩展、额外的库或iSyncEnumerable之类的东西。

我认为Rx肯定是这种情况下的解决方法,因为可观察序列是可枚举序列的形式对偶

正如在前面的回答中提到的,您可以从头开始将序列重新编写为可观察序列,但也有几种方法可以继续编写迭代器块,然后异步释放它们

1) 只需将可枚举项转换为可观察项,如下所示:

using System.Reactive.Linq;
using System.Reactive.Concurrency;

var enumerable = Enumerable.Range(10);
var observable = enumerable.ToObservable();
var subscription = observable.Subscribe(x => Console.WriteLine(x));
这将通过将其通知推送到任何下游观察者中,使您的可枚举对象的行为类似于可观察对象。在这种情况下,当Subscribe被调用时,它将同步阻塞,直到所有数据都被处理完毕。如果希望它完全异步,可以使用以下命令将其设置为其他线程:

var observable = enumerable.ToObservable().SubscribeOn(NewThreadScheduler.Default);
现在,将在新线程中展开枚举,subscribe方法将立即返回

2) 使用另一个文件展开可枚举文件
using System.Reactive.Linq;
using System.Reactive.Concurrency;

var enumerable = Enumerable.Range(10);
var observable = enumerable.ToObservable();
var subscription = observable.Subscribe(x => Console.WriteLine(x));
var observable = enumerable.ToObservable().SubscribeOn(NewThreadScheduler.Default);
var enumerable = Enumerable.Range(10);
var observable = Observable.Timer(TimeSpan.Zero, TimeSpan.FromSeconds(1))
                           .Zip(enumerable, (t, x) => x);
var subscription = observable.Subscribe(x => Console.WriteLine(x));