Rx:等待一段时间的第一项

时间:2017-10-02 11:11:43

标签: c# task-parallel-library system.reactive

我想将基于事件的遗留方法转换为基于观察的方法,但我对Rx很新,所以我现在陷入困境。

我有一个事件源,现在可以观察到。在某个时间点,我必须启动一个方法,该方法通过返回行中的下一个元素结束,如果超时则返回null。

基于事件的方法如下所示:

public async Task<ReaderEvent> WaitForReaderAsync(int PlaceId, TimeSpan waitFor)
{
    ReaderEvent result = null;
    using (var cts = CancellationTokenSource.CreateLinkedTokenSource(new [] { topLevelToken }))
    {
        cts.CancelAfter(waitFor);

        EventHandler<ReaderEvent> localHandler = (o, e) =>
        {
            if (e.PlaceId == PlaceId)
            {
                result = e;
                cts.Cancel();
            }
        };

        ReaderEventHandler += localHandler;
        try
        {
            await Task.Delay(waitFor, cts.Token).ConfigureAwait(false);
        }
        catch (OperationCanceledException) { }
        catch (Exception ex)
        {
            //...
        }

        ReaderEventHandler -= localHandler;
    }

    return result;
}

正如您所看到的,我们的想法是,延迟会在我等待的事件到达时取消,或者在特定时间后由配置取消令牌源。很干净。

现在,Rx版本:

public async Task<ReaderEvent> WaitForReaderAsync(int PlaceId, TimeSpan waitFor)
{
    ReaderEvent result = null;

    var observable = _OnReaderEvent.FirstAsync(r => r.PlaceId == PlaceId);

    using (var cts = CancellationTokenSource.CreateLinkedTokenSource(new [] { topLevelToken }))
    {
        cts.CancelAfter(waitFor);
        using (observable.Subscribe(x => {
            result = x;
            cts.Cancel();
        {
            try
            {
                await Task.Delay(waitFor, cts.Token).ConfigureAwait(false);
            }
            catch (OperationCanceledException) { }
        }
    }
    return result;
}

不太干净......更糟糕的是...... 我也尝试过Timeout扩展。但由于这是一次性订阅,我仍然需要在处理订阅之前以某种方式等待。唯一的区别是OnError会取消本地令牌,而不是CancelAfter的内置机制。

是否有任何击球手/更简洁(更依赖Rx)的方式来做到这一点?

谢谢!

3 个答案:

答案 0 :(得分:3)

你可以试试:

GST-17/18/SO-001311

答案 1 :(得分:1)

为什么不使用简单的Rx版代码:

public async Task<ReaderEvent> WaitForReaderAsync(int PlaceId, TimeSpan waitFor)
{
    return await
        _OnReaderEvent
            .Where(r => r.PlaceId == PlaceId)
            .Buffer(waitFor, 1)
            .Select(xs => xs.FirstOrDefault())
            .FirstOrDefaultAsync()
            .ToTask();
}

答案 2 :(得分:1)

这个问题可以用很多不同的方式解决。这是一个,使用 AmbReturnDelayFirstAsync 运算符:

public Task<ReaderEvent> WaitForReaderAsync(int PlaceId, TimeSpan waitFor)
{
    return _OnReaderEvent
        .Where(r => r.PlaceId == PlaceId)
        .Amb(Observable.Return(default(ReaderEvent)).Delay(waitFor))
        .FirstAsync()
        .ToTask();
}

在异常情况下,_OnReaderEvent observable 在等待期间完成或完成,产生的 Task 将转换为错误状态,异常为 InvalidOperationException "Sequence contains no元素”。

另一个实现,在功能上等同于前一个,使用 Timeout 运算符:

public Task<ReaderEvent> WaitForReaderAsync(int PlaceId, TimeSpan waitFor)
{
    return _OnReaderEvent
        .Where(r => r.PlaceId == PlaceId)
        .FirstAsync()
        .Timeout(waitFor, Observable.Return(default(ReaderEvent)))
        .ToTask();
}