У меня есть служба, которая позволяет вызывающему пользователю отправлять команды и получать ответы асинхронно. В реальном приложении эти действия довольно несвязаны (какое-то действие отправит команду, и ответы будут выполняться независимо).
Однако в моих тестах мне нужно отправить команду, а затем дождаться ответа (первого) перед продолжением теста.
Ответы публикуются с использованием 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);
}