在IObservable上使用ToTask时防止InvalidOperationException

时间:2019-07-10 14:36:37

标签: c# task observable system.reactive

我需要以某种方式防止在不产生任何值的Observable上调用ToTask时抛出的InvalidOperationException

我所看到的是ToTask方法创建了ToTaskObserver的一个实例,并且它希望至少一个值不引发异常:

private sealed class ToTaskObserver<TResult> : SafeObserver<TResult>
{
    //[...]
    private bool _hasValue;
    private TResult _lastValue;

    public ToTaskObserver(TaskCompletionSource<TResult> tcs, CancellationToken ct)
    {
       //[...]
    }

    public override void OnNext(TResult value)
    {
       _hasValue = true;
       _lastValue = value;
    }

    //[...]

    public override void OnCompleted()
    {
        if (_hasValue)
        {
            _tcs.TrySetResult(_lastValue);
        }
        else
        {
           _tcs.TrySetException(new InvalidOperationException(Strings_Linq.NO_ELEMENTS));
        }
        //[...]
    }
    //[...]
}

我发现的唯一解决方案是模仿扩展方法,并通过合并将虚拟记录插入Observable:

public static Task<Record<TKey, TValue>> ToTask<TKey, TValue>(
    this IObservable<Record<TKey, TValue>> source, CancellationToken token)
{
   var pseudoObservable = new[] {Record.Create<TKey, TValue>(default, default)}.ToObservable();
   return source.Merge(pseudoObservable).ToTask(token);
}

这个问题尤其与ToTask方法有关。我知道当我使用Subscribe方法时,我不会遇到这个问题。

有人对此有更好的解决方案吗?我在可观察的场景中将没有任何记录。

1 个答案:

答案 0 :(得分:0)

此处有许多选项:

  • 如果您期望有多个记录(并且可能不是因为等待这些记录效果不好),则可以使用.ToList()
  • 如果期望0或1条记录,并且default(T)不是有效值,则可以使用.LastOrDefaultAsync(),然后检查默认值。
  • 如果期望0或1条记录,并且default(T)是有效值,则仍然可以使用.ToList(),也可以使用Option样式类:

除非我忘记了C#的内置选项(F#有一个),否则Option类看起来像这样。

public class Optional<T>
{
    private readonly bool _hasValue;
    private readonly T _value;
    public Optional(T t)
    {
        _value = t;
        _hasValue = true;
    }

    public Optional()
    {
        _hasValue = false;
        _value = default(T);
    }

    public bool HasValue => _hasValue;
    public T Value 
    {
        get 
        {
            if(HasValue)
                return _value;
            else
                throw new InvalidOperationException("No value present");
        }
    }
}

public static class X { 
    public static IObservable<Optional<T>> ToOptional<T>(this IObservable<T> source)
    {
        return source.Publish(_source => _source
            .Select(t => new Optional<T>(t))
            .LastOrDefaultAsync()
            .Select(n => n.Equals(default(Optional<T>)) ? new Optional<T>() : n)
        );
    }
}

Optional相比,我更喜欢List选项,因为当您知道最大值为一个时,List看起来很有趣。但是取决于你。这是示例用法:

var fortyTwoList = await Observable.Return(42).ToList();
var noneList = await Observable.Empty<int>().ToList();
var fortyTwo = await Observable.Return(42).ToOptional();
var none = await Observable.Empty<int>().ToOptional();

none.Dump();        //Linqpad
fortyTwo.Dump();    //Linqpad
noneList.Dump();        //Linqpad
fortyTwoList.Dump();    //Linqpad