C# - 并发Foreach就像线程系统一样

时间:2017-07-02 11:11:55

标签: c# multithreading foreach concurrency system.reactive

IObservable<Match> IObservableArray = new Regex("(.*):(.*)").Matches(file).OfType<Match>().ToList().ToObservable();
var query = IObservableArray.SelectMany(s => Observable.Start(() => {
    //do stuff
}));

工作代码上面的说明:上面的代码使用带有Reactive的Observable来执行并发多线程系统,同时将s保留为匹配。

我的问题是,在开始执行//do stuff之前似乎需要将所有内容加载到内存中,因为IObservableArray是一个很大的匹配数组 - 这会占用大量内存导致它执行一个OutOfMemory例外。

我已经研究了一个多月了,我所能找到的就是.Buffer(),如果我把它放在.SelectMany()之前然后在s上匹配,我可以将1000匹配加载到内存中一段时间使整体记忆变得更好。

但是,因为我不得不求助于使用foreach一次遍历缓冲区中的所有1000个,所以它不是并发的 - 这意味着我基本上一个接一个地检查。

有没有办法在下面执行类似的代码,但是它有Concurrent / Multi-Threaded? (至少有150个并发运行,但不要将所有内容加载到内存中,目前使用1000个。)

是的,我尝试过使用thread.start等,使用它们会让它更早地触发完成的代码,因为从技术上来说它完成了,因为它已经完成了它被告知的所有内容使它们全部成为新线程

IObservable<Match> IObservableArray = new Regex("(.*):(.*)").Matches(file).OfType<Match>().ToList().ToObservable();
var query = IObservableArray.Buffer(1000).SelectMany(s => Observable.Start(() => {
    //do stuff
}));
query.ObserveOn(ActiveForm).Subscribe(x =>
{
    //do finish stuff
});

2 个答案:

答案 0 :(得分:0)

您实际上并没有告诉Start()使用哪个调度程序,这可能是您无法获得所需并发的原因。您可以将所需的调度程序指定为第二个参数:

var query = IObservableArray.Buffer(1000).SelectMany(s => Observable.Start(() => {
    //do stuff
}, TaskPoolScheduler.Default));

如果您知道//do stuff将花费超过500毫秒,我会考虑使用ThreadPoolScheduler。任务池不会产生新线程,直到Task阻塞线程至少500毫秒,所以如果你知道你将要做很多繁重的工作并需要大量的工作线程,您可以使用ThreadpoolScehduler.Instance代替TaskPoolScheduler.Default

答案 1 :(得分:0)

对于此类工作,IEnumerable<T>IObservable<T> 更匹配。可枚举是您可以按需展开的东西,并在您准备好处理它们时获取其值。相反,可观察对象是将其值强行推给您的东西,无论您是否能够处理负载。

有多种方法可以并行处理 IEnumerable<T>,并具有特定的并行度。在提出任何建议之前,要问的第一个问题是您必须对每个 Match 做的事情是同步的还是异步的。对于同步工作,最常用的工具是 Parallel 类、PLINQTPL Dataflow 库。下面是一个 PLINQ 示例:

IEnumerable<Match> matches = RegexFindAllMatches(file, "(.*):(.*)");
Partitioner
    .Create(matches, EnumerablePartitionerOptions.NoBuffering)
    .AsParallel()
    .WithDegreeOfParallelism(Environment.ProcessorCount)
    .ForAll(match =>
    {
        // Do stuff
    });
/// <summary>
/// Provides an enumerable whose elements are the successful matches found by
/// iteratively applying a regular expression pattern to the input string.
/// </summary>
public static IEnumerable<Match> RegexFindAllMatches(
    string input, string pattern, RegexOptions options = RegexOptions.None,
    TimeSpan matchTimeout = default)
{
    if (matchTimeout == default) matchTimeout = Regex.InfiniteMatchTimeout;
    var match = Regex.Match(input, pattern, options, matchTimeout);
    while (match.Success)
    {
        yield return match;
        match = match.NextMatch();
    }
}

上面的实现避免了使用 Regex.Matches 方法,以及随后的 MatchCollection 类,因为虽然这个类在枚举期间懒惰地评估下一个 Match,但它随后存储了每个找到的Match 在内部 ArrayList (source code) 中。这可能会导致大量内存分配,与匹配总数成正比。

对于异步工作,Parallel 类和 PLINQ 不是好的选择(除非您愿意等待 .NET 6 Parallel.ForEachAsync),但您仍然可以使用 TPL 数据流库。您还可以找到大量自定义选项 herehere。搜索 C# ForEachAsync 应该会显示更多选项。