У меня есть служба, которая позволяет вызывающему пользователю отправлять команды и получать ответы асинхронно. В реальном приложении эти действия довольно несвязаны (какое-то действие отправит команду, и ответы будут выполняться независимо).
Однако в моих тестах мне нужно отправить команду, а затем дождаться ответа (первого) перед продолжением теста.
Ответы публикуются с использованием RX, и моя первая попытка кода была примерно такой:
service.SendCommand("BLAH");
await service.Responses.FirstAsync();
Проблема с этим заключается в том, что FirstAsync будет работать, только если ответ поступит после этого ожидания, который уже был атакован. Если служба работает очень быстро, тест будет висеть на await.
Моя следующая попытка исправить это состояла в том, чтобы вызвать FirstAsync() перед отправкой команды, чтобы она имела результат, даже если она прибыла до ожиданий:
var firstResponse = service.Responses.FirstAsync();
service.SendCommand("BLAH");
await firstResponse;
Однако это все равно не срабатывает аналогичным образом. Похоже, что только когда await попадает (GetAwaiter), который он начинает слушать; поэтому существует то же самое условие гонки.
Если я изменю тему Subject на ReplaySubject с буфером (или таймером), тогда я могу "обходить" это; однако в моих производственных классах нет смысла делать это; это будет только для тестирования.
Какой "правильный" способ сделать это в RX? Как я могу настроить то, что получит первое событие в потоке таким образом, чтобы не вводить условие гонки?
Вот небольшой тест, который иллюстрирует проблему в однопоточном режиме. Этот тест будет висеть неопределенно:
[Fact]
public async Task MyTest()
{
var x = new Subject<bool>();
// Subscribe to the first bool (but don't await it yet)
var firstBool = x.FirstAsync();
// Send the first bool
x.OnNext(true);
// Await the task that receives the first bool
var b = await firstBool; // <-- hangs here; presumably because firstBool didn't start monitoring until GetAwaiter was called?
Assert.Equal(true, b);
}
Я даже попытался вызвать Replay() в своем тесте, считая, что он будет буферизовать результаты; но это ничего не меняет:
[Fact]
public async Task MyTest()
{
var x = new Subject<bool>();
var firstBool = x.Replay();
// Send the first bool
x.OnNext(true);
// Await the task that receives the first bool
var b = await firstBool.FirstAsync(); // <-- Still hangs here
Assert.Equal(true, b);
}