I have a .NET 5 ReactiveUI application that launches a process. I am trying to get real time logs from the process to a ListBox. I have had some success getting the logs, but only after the process ends; then I get all of the logs at one time. I have observed that the MessageBus listener I created is called in realtime, but the UI is not reflecting the update until after the process ends. Below are some code snippets.
Any help will be appreciated. Thank you in advance!
Process Code:
public void Execute(Payload payload)
{
using Process process = new()
{
StartInfo = CreateStartInfo(payload)
};
process.ErrorDataReceived += delegate(object sender,
DataReceivedEventArgs e)
{
MessageBus.Current.SendMessage(new LogEventArgs(e.Data));
};
process.OutputDataReceived += delegate(object sender,
DataReceivedEventArgs e)
{
MessageBus.Current.SendMessage(new LogEventArgs(e.Data));
};
MessageBus.Current.SendMessage(new LogEventArgs("Starting Process."));
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
StreamWriter myStreamWriter = process.StandardInput;
CancellationTokenSource tokenSource = new();
CancellationToken token = tokenSource.Token;
Task.Run(() =>
{
while (true)
{
myStreamWriter.WriteLine();
Thread.Sleep(250);
if (token.IsCancellationRequested)
{
break;
}
}
},
tokenSource.Token);
process.WaitForExit();
MessageBus.Current.SendMessage(new LogEventArgs("Process Finished."));
tokenSource.CancelAfter(500);
}
private static ProcessStartInfo CreateStartInfo(Payload payload)
{
string applicationPath = Path.Combine(payload.Directory,
"Application");
return new ProcessStartInfo
{
FileName = $"{applicationPath}.exe",
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = false,
RedirectStandardError = true,
RedirectStandardOutput = true,
RedirectStandardInput = true
};
}
ViewModel Code:
public class LogPaneViewModel : ReactiveObject
{
public ListBox LogListBox { get; set; }
[Reactive]
public ObservableCollection<string> LogsList { get; set; } = new();
[Reactive]
public ReactiveCommand<Unit, Unit> ClearLogs { get; set; }
public LogPaneViewModel()
{
MessageBus.Current.Listen<LogEventArgs>()
.Subscribe(OnLogMessageReceived);
ClearLogs = ReactiveCommand.Create(OnClearLogClicked);
}
private void OnLogMessageReceived(LogEventArgs args)
{
// *** This is called in real time by the process.
// But the ListBox is not updating until the process ends.
LogsList.Add(args.Text);
LogListBox.ScrollIntoView(args.Text);
}
private void OnClearLogClicked()
{
LogsList.Clear();
}
}
Code Behind Code:
public partial class LogPane : ReactiveUserControl<LogPaneViewModel>
{
public LogPane()
{
InitializeComponent();
ViewModel = App.ServiceProvider.GetRequiredService<LogPaneViewModel>();
ViewModel.LogListBox = LogsList;
this.WhenActivated(disposableRegistration =>
{
this.OneWayBind(ViewModel,
vm => vm.LogsList,
view => view.LogsList.ItemsSource,
s => s)
.DisposeWith(disposableRegistration);
this.BindCommand(ViewModel,
vm => vm.ClearLogs,
view => view.ClearButton)
.DisposeWith(disposableRegistration);
});
}
}