-1

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);
            });
        }
    }
jecjackal
  • 1,407
  • 2
  • 20
  • 35
  • Looks like you might be blocking the UI thread somewhere. Probably in your `public void Execute(Payload payload)` method. Try wrapping the entire thing in a `Task.Run`. Also the way you do your delegates in that class, there are newer lambda delegate which is much simpler `(sender, args) => ` rather than declaring delegate. – Glenn Watson May 26 '21 at 23:39
  • @jecjackal: Where, how and when is `Execute` invoked? – mm8 May 27 '21 at 13:51
  • @GlennWatson you were right, I was blocking the UI. That's what I get for coding late at night. Ty! – jecjackal May 28 '21 at 00:57

1 Answers1

1

My process code waits until the process was finished. This led to the thread blocking the UI thread. As GlennWatson suggested and mm8 hinted, this was solved by wrapping the launching of that class in Task.Run(). Thank you for the help!

jecjackal
  • 1,407
  • 2
  • 20
  • 35