2

I have created the following code. I supposed it would work, but it doesn't. The await hangs there indefinitely (Exit event is never called).

I'm calling "ping" with no arguments, so it would end almost immediately.

using System;
using System.Diagnostics;
using System.Reactive.Linq;
using System.Threading.Tasks;

namespace ConsoleApp3
{
    class Program
    {
        static async Task Main(string[] args)
        {
            var process = new Process
            {
                StartInfo =
                {
                    FileName = "ping", 
                    UseShellExecute = false, 
                    CreateNoWindow = true,
                    RedirectStandardOutput = true, 
                    RedirectStandardError = true,                    

                },
                EnableRaisingEvents = true
            };


            var obs = Observable.FromEventPattern(handler => process.Exited += handler, handler => process.Exited -= handler);

            var started = process.Start();
            if (!started)
            {
                throw new InvalidOperationException("Could not start process: " + process);
            }

            await obs.FirstAsync();
        }
    }
}

How can I make it work using IObservable?

halfer
  • 19,824
  • 17
  • 99
  • 186
SuperJMN
  • 13,110
  • 16
  • 86
  • 185

1 Answers1

2

You have a race condition here. A quote from this RX repro thread:

FirstAsync returns a cold observable. It’s not until you subscribe to it or await that you’ll receive messages.

Unlike TaskCompletionSource (the use of which might be more appropriate here, IMO), the observable returned by FirstAsyncdoesn't "cache" the event that had occurred before you activated the subscription by awaiting it. So, process.Exited event gets triggered before await obs.FirstAsync() and thus the .NET startup code blocks on the task returned by Main.

The following would work as expected (you'd need to add using System.Reactive.Threading.Tasks):

var task = obs.FirstAsync().ToTask();

var started = process.Start();
if (!started)
{
    throw new InvalidOperationException("Could not start process: " + process);
}

await task;

Here we activate the observable subscription by converting it to a Task, before starting the process.

If interested in something like Observable.FromEventPattern but for TaskCompletionSource, check this Q/A.

noseratio
  • 59,932
  • 34
  • 208
  • 486