简体   繁体   English

以无特定顺序将两个无限 C# IEnumerables 连接在一起

[英]Concatenate two infinite C# IEnumerables together in no particular order

I have two methods that each return infinite IEnumerable that never ends.我有两种方法,每个方法都返回永无止境的无限IEnumerable I want to concatenate them so whenever any of the IEnumerable s return a value, I can instantly get and process it.我想将它们连接起来,这样每当任何IEnumerable返回一个值时,我都可以立即获取并处理它。

        static void Main(string[] args)
        {
            var streamOfBoth = get1().Concat(get2());
            foreach(var item in streamOfBoth)
            {
                Console.WriteLine(item);
               // I'd expect mixed numbers 1 and 2
               // Instead I receive only 1s
            }
         }

        static IEnumerable<int> get1()
        {
            while (true)
            {
                System.Threading.Thread.Sleep(1000);
                yield return 1;
            }
        }

        static IEnumerable<int> get2()
        {
            while (true)
            {
                System.Threading.Thread.Sleep(200);
                yield return 2;
            }
        }

Is there a way to do this with IEnumerables without having to use threads?有没有办法用 IEnumerables 做到这一点而不必使用线程?

This is fairly easily achieved with System.Reactive这很容易通过System.Reactive实现

static void Main()
{
    get1().ToObservable(TaskPoolScheduler.Default).Subscribe(Print);
    get2().ToObservable(TaskPoolScheduler.Default).Subscribe(Print);
}

static void Print(int i)
{
    Console.WriteLine(i);
}

static IEnumerable<int> get1()
{
    while (true)
    {
        System.Threading.Thread.Sleep(1000);
        yield return 1;
    }
}

static IEnumerable<int> get2()
{
    while (true)
    {
        System.Threading.Thread.Sleep(200);
        yield return 2;
    }
}

This produces the following output on my machine:这会在我的机器上产生以下输出:

2 2 2 2 1 2 2 2 2 2 1 2 2 2 2 2 1 2 2 2 2 2 1 2 2 2 2 2 1 2 2 2 2 2 1 ...

Note that ToObservable is called with the argument TaskPoolScheduler.Default ;需要注意的是ToObservable时调用参数TaskPoolScheduler.Default ; just calling ToObservable without it will result in synchronous execution, meaning it will keep enumerating the first sequence forever and never get to the second one.ToObservable没有它的情况下调用ToObservable将导致同步执行,这意味着它将永远枚举第一个序列而永远不会到达第二个序列。

You may want to interleave get1 and get2 (take item from get1 , then from get2 , then again from get1 and then from get2 etc.).您可能希望交织get1get2 (从拿项目get1 ,然后从get2从,然后再get1 ,然后从get2等)。 Generalized ( IEnumerable<T> are not necessary infinite and of the same size) Interleave<T> extension method can be like this:广义( IEnumerable<T>不一定是无限的且大小相同) Interleave<T>扩展方法可以是这样的:

   public static partial class EnumerableExtensions {
     public static IEnumerable<T> Interleave<T>(this IEnumerable<T> source, 
                                                params IEnumerable<T>[] others) {
      if (null == source)
        throw new ArgumentNullException(nameof(source));
      else if (null == others)
        throw new ArgumentNullException(nameof(others));

      IEnumerator<T>[] enums = new IEnumerator<T>[] { source.GetEnumerator() }
          .Concat(others
          .Where(item => item != null)
          .Select(item => item.GetEnumerator()))
        .ToArray();

      try {
        bool hasValue = true;

        while (hasValue) {
          hasValue = false;

          for (int i = 0; i < enums.Length; ++i) {
            if (enums[i] != null && enums[i].MoveNext()) {
              hasValue = true;

              yield return enums[i].Current;
            }
            else {
              enums[i].Dispose();
              enums[i] = null;
            }
          }
        }
      }
      finally {
        for (int i = enums.Length - 1; i >= 0; --i)
          if (enums[i] != null)
            enums[i].Dispose();
      }
    }
  }

Then use it:然后使用它:

   var streamOfBoth = get1().Interleave(get2());

   foreach(var item in streamOfBoth)
   {
       Console.WriteLine(item);
   }

Edit: if编辑:如果

" whenever any ... return a value, I can instantly get and process" 只要有任何......返回一个值,我就可以立即获取并处理”

is crucial phrase in your question you can try BlockingCollection and implementproducer-consumer pattern :是您问题中的关键短语,您可以尝试BlockingCollection并实现生产者-消费者模式

static BlockingCollection<int> streamOfBoth = new BlockingCollection<int>();

// Producer #1 
static void get1() {
  while (true) {
    System.Threading.Thread.Sleep(1000);

    streamOfBoth.Add(1); // value (1) is ready and pushed into streamOfBoth
  }
}

// Producer #2
static void get2() {
  while (true) {
    System.Threading.Thread.Sleep(200);

    streamOfBoth.Add(2); // value (2) is ready and pushed into streamOfBoth
  }
}

...

Task.Run(() => get1()); // Start producer #1
Task.Run(() => get2()); // Start producer #2

...

// Cosumer: when either Producer #1 or Producer #2 create a value
// consumer can starts process it 
foreach(var item in streamOfBoth.GetConsumingEnumerable()) {
  Console.WriteLine(item);
}

Here is a generic Merge method that merges IEnumerable s.这是合并IEnumerable的通用Merge方法。 Each IEnumerable is enumerated in a dedicated thread.每个IEnumerable都在一个专用线程中枚举。

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

public static IEnumerable<T> Merge<T>(params IEnumerable<T>[] sources)
{
    IEnumerable<IObservable<T>> observables = sources
        .Select(source => source.ToObservable(NewThreadScheduler.Default));
    IObservable<T> merged = Observable.Merge(observables);
    return merged.ToEnumerable();
}

Usage example:用法示例:

var streamOfBoth = Merge(get1(), get2());

Enumerating the resulting IEnumerable will block the current thread until the enumeration is finished.枚举生成的IEnumerable将阻塞当前线程,直到枚举完成。

This implementation depends on the System.Reactive and System.Interactive.Async packages.此实现取决于System.ReactiveSystem.Interactive.Async包。

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

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM