为什么Replay方法会阻止Observable集合完成?

时间:2017-05-14 04:45:50

标签: c# roslyn rx.net

我在项目列表上执行的任务很慢。 这就是我想要实现的目标:

  1. 应立即开始处理
  2. 最多N个并发任务
  3. 任务只应每个项目运行一次
  4. 所有订阅者都应该看到所有结果(无论他们何时订阅)
  5. 这是我的原型 - 它按预期工作(LinqPad)

    async Task Main()
    {
         int throttle = 3;
    
        var result =
            Enumerable.Range(1, 20)
            .ToObservable()
            .Select(n => Observable.FromAsync(() => DoSomething(n)))
            .Merge(throttle)
            .Replay();
    
        Task.Run(async () => await result);
        Util.KeepRunning();
    }
    
    public async Task DoSomething(int n)
    {
        Console.WriteLine($"Starting {n}");
        await Task.Delay(100);
        Console.WriteLine($"Ending {n}");
    }
    

    这是我的实际代码:

        sln.Projects = result
            .Projects
            .ToObservable() //Appears to be unnecessary
            .Select(p => Observable.FromAsync(() => projectLoader.Load(p, sln)))
            .Merge(_maxConcurrent)
            .Replay(); 
    
         await sln.Projects; //Hangs forever if Replay is used
    

    "导致"是Microsoft.CodeAnalysisSolution

    我尝试了几种变体 -
        Projects.ToList,ToObservable和没有     用假负载替换项目加载器
        有和没有_maxConcurrent

    这段代码在等待中永远挂起,我似乎无法看到与原型在功能上有什么不同。

    如果我删除重播,代码会按预期运行,但会被每个订阅者点击。

    有没有人知道这段代码是如何不同的,以及为什么Replay方法会影响这样的执行?

    我开始怀疑它必须是订阅者的问题 - 但目前我无法轻易提取这些内容以进行验证。

    以下是可行的原始代码(但不会扼制)

        sln.Projects = result.Projects
            .Select(p => projectLoader.Load(p, sln))
            .ToList()
            .Select(p => p.ToObservable())
            .Merge();
    

1 个答案:

答案 0 :(得分:0)

原型与实际使用代码之间的区别在于存储observable的方式。在您的原型中,它存储为局部变量,但在您的生产代码中,您将其保留在sln对象中。

为了能够为将来的订阅者重放观察中的所有信号,它无法完成。我认为它与局部变量一起使用的原因与局部范围有关,但我还没有挖掘它。

如果在Linqpad中运行以下命令,您将看到将observable存储在对象中会改变行为。

async Task Main()
{
     int throttle = 3;
    var container = new Container();

    container.Results =
        Enumerable.Range(1, 20)
            .ToObservable()
            .Select(n => Observable.FromAsync(() => DoSomething(n)))
            .Merge(throttle)
            .Replay();

    var res = await container.Results;

    res.Dump("Res");
}

public async Task DoSomething(int n)
{
    Console.WriteLine($"Starting {n}");
    await Task.Delay(100);
    Console.WriteLine($"Ending {n}");
}

public class Container
{
    public IObservable<Unit> Results { get; set; }
}

Replay()会返回IConnectableObservable,为了生成此可观察的返回值,您必须在其上调用Connect()。这样做使上面的代码适合我:

async Task Main()
{
     int throttle = 3;
    var container = new Container();

    container.Results =
        Enumerable.Range(1, 20)
        .ToObservable()
        .Select(n => Observable.FromAsync(() => DoSomething(n)))
        .Merge(throttle)
        .Replay();

    container.Results.Connect();

    (await container.Results).Dump("1");
    (await container.Results).Dump("2");
}

public async Task DoSomething(int n)
{
    Console.WriteLine($"Starting {n}");
    await Task.Delay(100);
    Console.WriteLine($"Ending {n}");
}

public class Container
{
    public IConnectableObservable<Unit> Results { get; set; }
}