请跟我一起轻松,我也在学习这个话题,我真的很享受。所以这里......
当有足够的数据进入并表示" IMyEvent"时,我会从流中创建一个可观察的读数。我需要触发的类型,我将构建该类型并调用observer.OnNext。
此流是来自服务器的响应流,其中命令从我自己或外部发送到此服务器,因此我可以读取此流并基于此做出反应。
对于我发送的每个命令,我订阅了从此流制作的observable。我可以看看我的命令是否已成功完成。我还在外部订阅此流,并对可能发生的其他事件做出反应。
让我们举一个例子,让我们说有人在外部加入这个服务器的会议,服务器将向下发送数据流,然后我的Observable捕获这个并调用onNext of observer。我想基于此做出反应并锁定会议。 在此编辑... 锁定环境的意思是我向服务器发送一个Lock命令,服务器知道"锁定自己"允许其他人加入会议。
我有一个客户端,总是订阅" SomeoneJoinsEvent" (他们订阅了可观察流)在此订阅中执行此onNext时,我还会触发一个命令来锁定会议。此命令也会临时订阅(我使用超时运算符)到同一个可观察的流,但我注意到我在这里被阻止/锁定。
我可以看到,当我执行onNext时,它不会继续读取流以观察更多的IMyEvents。
所以我的想法/想法/脑筋跟着...... 有没有一种特殊的方式我可以以某种方式解耦这个Observable流,所以它不断读取并向所有订阅者发起OnNext并且不等待它们完成?或者在中间暴露一些与主Observable流安全地分离的东西。
有人可以帮助我吗? 我希望这对我想做的事情有意义。
以下是我的代码示例...
//This is the observable that I create from the stream of events
public IObservable<IMyEvent> MyEvents()
{
if (_myEventObservable != null)
{
return _myEventObservable;
}
// Here is one observable that reads data as it comes in off the asynchronous stream.
_myEventObservable = Observable.Create<IMyEvent>(async observer =>
{
var myServerEventStream = await GetStreamFromMyServer(_myAuthenticationConfiguration, _token, _httpClientWrapper);
var streamReaderAsyncState = _streamReaderAsyncStateFactory.CreateStreamReadAsyncState(myServerEventStream, 255);
var currentStateDisposable = new SerialDisposable();
var iterator = new Action<IStreamReaderAsyncState, Action<IStreamReaderAsyncState>>((state, self) =>
{
try
{
currentStateDisposable.Disposable = state.StreamObservable().Subscribe(
//OnNext
bytesRead =>
{
if (bytesRead == 0)
{
observer.OnCompleted();
}
else
{
//In here it does all the magic to put all the data together and only call OnNext on the observer when there is a complete IMyEvent.
//It is just a plain observer.OnNext(IMyEvent whatever event we have build up)
_messageParser.Parse(new DataChunk(state.Buffer, bytesRead), _token, observer);
self(state);
}
});
}
catch (Exception e)
{
observer.OnError(e);
}
});
var schedulerDisposable = TaskPoolScheduler.Default.Schedule(streamReaderAsyncState, iterator);
return new CompositeDisposable(myServerEventStream, schedulerDisposable, currentStateDisposable);
}).Publish().RefCount();
return _myEventObservable;
}
//Just for fuller visibility on the stream reader async state, here is the class that is returned from the "_streamReaderAsyncStateFactory.CreateStreamReadAsyncState(myServerEventStream, 255)" call
internal class StreamReaderAsyncState : IStreamReaderAsyncState
{
private readonly IObservable<int> _readAsyncObservable;
public StreamReaderAsyncState(Stream stream, int bufferSize)
{
Buffer = new byte[bufferSize];
_readAsyncObservable = Observable.FromAsync(() => stream.ReadAsync(Buffer, 0, bufferSize));
}
public byte[] Buffer { get; private set; }
public IObservable<int> StreamObservable()
{
return _readAsyncObservable;
}
}
//Externally I subscribe to this like so...
MyEvents.OfType<SomoneJoinsEvent>
.Subscribe(
//I read somewhere that I shouldn't be making this async and using a selectmany with the async, but I am unsure.
async myEvent => {
await LockEnvironment()
}
)
//The LockEnvironment call
//The myCommandPost is the LockEnvironment Command that is passed in.
private async Task<CommandResponse<TCommandResponseDto>> PostCommandAndWaitForEvent<TEventToWaitFor, TCommandResponseDto>(IMyCommandPost myCommandPost)
where TEventToWaitFor : IMyEvent
{
//So my intention here is to zip together the result of the post command with the TEventToWaitFor and return the first one. Otherwise if it takes too long it will return the result of the Timeout operator.
return await MyEvents()
.OfType<TEventToWaitFor>()
//This myCommandPost.PostCommand... returns a Task<CommandResponse<TCommandResponseDto>>
.Zip(myCommandPost.PostCommand<TCommandResponseDto>().ToObservable(), (myEvent, myCommandResponse) => myCommandResponse)
.Timeout(new TimeSpan(0, 0, _myAuthenticationConfiguration.TimeoutToWaitForCommands), TimedOutLookingForMyEvent<TCommandResponseDto>())
.FirstAsync();
}
//The timeout observable
private IObservable<CommandResponse<TCommandResponseDto>> TimedOutLookingForMyEvent<TCommandResponseDto>()
{
return Observable.Create<CommandResponse<TCommandResponseDto>>(observable =>
{
observable.OnNext(new CommandResponse<TCommandResponseDto>());
return Disposable.Empty;
});
}
此处也进行了编辑,添加了我为事件解析器执行的操作...
internal class MyEventParser : IMessageParser
{
private readonly ILogService _logService;
private readonly IMyEventFactory _MyEventFactory;
private readonly StringBuilder _data = new StringBuilder();
private const string EventDelimiter = "\n\n";
private readonly Regex _eventDelimiterRegex = new Regex("\n{3,}");
public MyEventParser(ILogService logService, IMyEventFactory myEventFactory)
{
_logService = logService;
_myEventFactory = myEventFactory;
}
public void Parse(DataChunk dataChunk, string token, IObserver<IMyEvent> myEventObserver)
{
_data.Append(dataChunk);
CleanUpEventDelimiterInData();
var numberOfSubstrings = CountNumberOfSubstrings(EventDelimiter, _data.ToString());
if (numberOfSubstrings == 0)
{
return;
}
var events = _data.ToString().Split(new[]{EventDelimiter}, StringSplitOptions.RemoveEmptyEntries);
events.Take(numberOfSubstrings).Foreach(x =>
{
_logService.InfoFormat("MyEventParser - {0} - OnNext: \n\n{1}\n\n", token.Substring(token.Length -10), x);
myEventObserver.OnNext(_myEventFactory.Create(x));
});
//Clean up data of what has already been fired.
if (events.Count() == numberOfSubstrings)
{
_data.Clear();
}
else
{
_data.Clear();
_data.Append(events.Last());
}
}
private void CleanUpEventDelimiterInData()
{
var eventDelimitersFixed = _eventDelimiterRegex.Replace(_data.ToString(), EventDelimiter);
_data.Clear();
_data.Append(eventDelimitersFixed);
}
private int CountNumberOfSubstrings(string subString, string source)
{
var i = 0;
var count = 0;
while ((i = source.IndexOf(subString, i, StringComparison.InvariantCulture)) != -1)
{
i += subString.Length;
count++;
}
return count;
}
}
提前感谢您的帮助: - )
答案 0 :(得分:2)
首先,欢迎使用Rx和反应式编程。 一开始可能会让人感到困惑,但是获得基本权利会使指数变得更容易。
首先,我想快速查看您的代码
public IObservable<IMyEvent> MyEvents()
{
if (_myEventObservable != null)
{
return _myEventObservable;
}
这看起来应该是具有private readonly
支持字段的属性。
可观察的序列被懒惰地评估,因此如果没有人订阅,则不会运行任何代码。
如果我有许多此Observable流的外部订阅者并且其中一个阻止了该怎么办?
那么你有一个表现不好的观察者。 Rx Observable序列的订阅者应该尽可能快地处理它们的回调。 这意味着没有锁定,没有IO,没有CPU密集处理。 如果你需要这样做,那么也许你需要排队一条消息,并在某个地方/其他时间做这项工作。
似乎StreamReaderAsyncState
做得不够,MyEvents
做得太多了。
也许你有这样的东西(从https://github.com/LeeCampbell/RxCookbook/tree/master/IO/Disk修改)
public static class ObservableStreamExtensions
{
public static IObservable<byte[]> ToObservable(this Stream source, int bufferSize)
{
return Observable.Create<byte[]>(async (o, cts) =>
{
var buffer = new byte[bufferSize];
var bytesRead = 0;
do
{
try
{
bytesRead = await source.ReadAsync(buffer, 0, bufferSize);
if (bytesRead > 0)
{
var output = new byte[bytesRead];
Array.Copy(buffer, output, bytesRead);
o.OnNext(output);
}
}
catch (Exception e)
{
o.OnError(e);
}
}
while (!cts.IsCancellationRequested && bytesRead > 0);
if (!cts.IsCancellationRequested && bytesRead == 0)
{
o.OnCompleted();
}
});
}
}
然后您的代码缩减为
private readonly IObservable<IMyEvent> _myEvents;
public ctor()
{
_myEvents = return Observable.Create<IMyEvent>(async observer =>
{
var bufferSize = 255;
var myServerEventStream = await GetStreamFromMyServer(_pexipAuthenticationConfiguration, _token, _httpClientWrapper);
var subscription = myServerEventStream
.ToObservable(bufferSize)
.Select(buffer=>new DataChunk(buffer, _token))
.Subscribe(observer);
return new CompositeDisposable(myServerEventStream, subscription);
})
.Publish()
.RefCount();
}
//This is the observable that I create from the stream of events
public IObservable<IMyEvent> MyData{ get { return _myEvents; } }
请注意,new DataChunk
现在不需要IObserver
,这对我来说就像代码味道。
另请注意,您在评论 shudder 中使用了“magic”一词。
这些都不应该是魔术。
只是一个订阅者和转换的组合。
接下来我们来看看你的问题的根源:锁定。 我不知道“锁定环境”意味着什么,你似乎没有解释或证明它。
也许你想要做的是拥有异步门的概念。 这样您只需设置一个标志来指定您所处的状态。 理想情况下,此标志也是可观察的。 然后,您可以使用该标志的状态组合其他传入事件/命令/消息,而不是锁定系统。
观看Matt在这里谈论异步门的视频(转到时间00:18:00):https://yow.eventer.com/yow-2014-1222/event-driven-user-interfaces-by-matt-barrett-and-lee-campbell-1686
回到我相信成为你的实际问题,我认为你已经创造了死锁。
MyEvents.OfType<SomoneJoinsEvent>
.Subscribe(
//I read somewhere that I shouldn't be making this async and using a selectmany with the async, but I am unsure.
async myEvent => {
await LockEnvironment()
}
)
此代码主动阻止生产者回调。 Rx是一个免费的线程模型,因此它实际上非常简单(理论上)它在幕后做了什么。 当调用OnNext时,它只是遍历每个订阅者并调用其回调。 在这里你是阻塞的,所以不仅生产者不能调用下一个订阅者,它也不能处理下一个消息。
所以你正在听MyEvents
序列,你会得到SomoneJoinsEvent
,就像第100条消息一样。
然后尝试按下将在MyEvents
中生成事件的命令。
收到此事件后,您将继续。
然而,您阻止该邮件被收到。
因此,你处于僵局。
所以现在回答你的问题是,你希望这个LockEnvironment真正实现什么?
编辑:
查看您的代码,似乎过于复杂。 你似乎经常使用StringBuilder,添加,查询变异并替换它的内容。
我认为您可以使用更通用的解决方案解决问题。
以下是如何处理正在读入缓冲区然后投影/转换为记录的流的示例。
var testScheduler = new TestScheduler();
//A series of bytes/chars to be treated as buffer read from a stream (10 at a time).
// a \n\n represents a record delimiter.
var source = testScheduler.CreateColdObservable<char[]>(
ReactiveTest.OnNext(0100, new char[] { 'a', 'b', 'c', 'd', 'e', 'f', 'g', '\n', '\n', 'h' }),
ReactiveTest.OnNext(0200, new char[] { 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', '\n', '\n' }),
ReactiveTest.OnNext(0300, new char[] { 'q', 'r', 's', '\n', '\n', 't', 'u', 'v', '\n', '\n' }),
ReactiveTest.OnNext(0400, new char[] { 'w', 'x', '\n', 'y', 'z', '\n', '\n' })
);
var delimiter = '\n';
var observer = testScheduler.CreateObserver<string>();
var shared = source.SelectMany(buffer=>buffer).Publish().RefCount();
var subscription = shared
//Where we see two '\n' values then emit the buffered values
.Buffer(() => shared.Scan(Tuple.Create(' ',' '), (acc, cur)=>Tuple.Create(acc.Item2, cur)).Where(t => t.Item1 == delimiter && t.Item2==delimiter))
//Remove trailing delimiters
.Select(chunk =>
{
var len = chunk.Count;
while(chunk[chunk.Count-1]==delimiter)
{
chunk.RemoveAt(chunk.Count-1);
}
return chunk;
})
//Filter out empty buffers
.Where(chunk=>chunk.Any())
//Translate the record to the desired output type
.Select(chunk=>new string(chunk.ToArray()))
.Subscribe(observer);
testScheduler.Start();
observer.Messages.AssertEqual(
ReactiveTest.OnNext(0100, "abcdefg"),
ReactiveTest.OnNext(0200, "hijklmnop"),
ReactiveTest.OnNext(0300, "qrs"),
ReactiveTest.OnNext(0300, "tuv"),
ReactiveTest.OnNext(0400, "wx\nyz")
);