C# 4.0 创建IConnectableObservable<;字符串>;来自进程-输出缓冲区问题
下面是我将System.Diagnostics.Process转换为IConnectableObservable的尝试。 此解决方案有一个问题:我希望连续地侦听标准输出和错误,并使用事件进程.Exited作为OnCompleted的触发器。不幸的是,我发现进程.Exited在输出缓冲区为空之前被提升。这意味着,如果没有线程睡眠这一难看的解决方法,我可以重现输出不通过OnNext语句提供服务的情况 问题1:你认为这个问题有什么解决办法吗 问题2:关于系统。被动:在我的解决方案中,我可以做得更好吗 问候, 马库斯C# 4.0 创建IConnectableObservable<;字符串>;来自进程-输出缓冲区问题,c#-4.0,system.reactive,reactive-programming,C# 4.0,System.reactive,Reactive Programming,下面是我将System.Diagnostics.Process转换为IConnectableObservable的尝试。 此解决方案有一个问题:我希望连续地侦听标准输出和错误,并使用事件进程.Exited作为OnCompleted的触发器。不幸的是,我发现进程.Exited在输出缓冲区为空之前被提升。这意味着,如果没有线程睡眠这一难看的解决方法,我可以重现输出不通过OnNext语句提供服务的情况 问题1:你认为这个问题有什么解决办法吗 问题2:关于系统。被动:在我的解决方案中,我可以做得更好吗
public static class RxProcessUtilities
{
/// <summary>
/// Creates a connectable observable for a process.
/// </summary>
/// <remarks>Must be a connectable observable in order to hinder multiple
/// subscriptions to call the process multiple times.</remarks>
/// <param name="process">The process.</param>
/// <returns></returns>
public static IConnectableObservable<string> CreateConnectableObservableProcess
(string filename, string arguments, IObservable<string> input = null)
{
var observable = Observable.Using(() =>
{
Process process = new Process();
// process configuration
process.StartInfo.FileName = filename;
process.StartInfo.Arguments = arguments;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.UseShellExecute = false;
process.EnableRaisingEvents = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.RedirectStandardOutput = true;
if (null != input)
{
process.StartInfo.RedirectStandardInput = true;
input.Subscribe(s =>
{
if (!process.HasExited)
{
process.StandardInput.Write(s);
}
});
}
return process;
},
process =>
{
return Observable.Create<string>(
(IObserver<string> observer) =>
{
// listen to stdout and stderr
var stdOut = RxProcessUtilities.CreateStandardOutputObservable(process);
var stdErr = RxProcessUtilities.CreateStandardErrorObservable(process);
var stdOutSubscription = stdOut.Subscribe(observer);
var stdErrSubscription = stdErr.Subscribe(observer);
var processExited = Observable.FromEventPattern
(h => process.Exited += h, h => process.Exited -= h);
var processError = processExited.Subscribe(args =>
{
// Here is my problem: process sends exited event *before* all
// *DataReceived events have been raised
// My ugly workaround for process exit before stdout and stderr buffers are empty.
Thread.Sleep(2000);
// Also: AFAICS we cannot read synchronously what is left in the buffer,
// since we started asynchronously. This will throw:
// string restOfStdOut = process.StandardOutput.ReadToEnd();
// string restOfStdErr = process.StandardError.ReadToEnd();
if (process.ExitCode != 0)
{
observer.OnError(new Exception
(String.Format("Process '{0}' terminated with error code {1}",
process.StartInfo.FileName, process.ExitCode)));
}
else
{
observer.OnCompleted();
}
});
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
return new CompositeDisposable
(stdOutSubscription,
stdErrSubscription,
processError);
});
});
return observable.Publish();
}
/// <summary>
/// Creates an IObservable<string> for the standard error of a process.
/// </summary>
/// <param name="process">The process.</param>
/// <returns></returns>
public static IObservable<string> CreateStandardErrorObservable(Process process)
{
// var processExited = Observable.FromEventPattern
// (h => process.Exited += h, h => process.Exited -= h);
var receivedStdErr =
Observable.FromEventPattern<DataReceivedEventHandler, DataReceivedEventArgs>
(h => process.ErrorDataReceived += h,
h => process.ErrorDataReceived -= h)
//.TakeUntil(processExited)
// cannot be used here, since process exited event might be raised
// before all stderr and stdout events occurred.
.Select(e => e.EventArgs.Data);
return Observable.Create<string>(observer =>
{
var cancel = Disposable.Create(process.CancelErrorRead);
return new CompositeDisposable(cancel, receivedStdErr.Subscribe(observer));
});
}
/// <summary>
/// Creates an IObservable<string> for the standard output of a process.
/// </summary>
/// <param name="process">The process.</param>
/// <returns></returns>
public static IObservable<string> CreateStandardOutputObservable(Process process)
{
var receivedStdOut =
Observable.FromEventPattern<DataReceivedEventHandler, DataReceivedEventArgs>
(h => process.OutputDataReceived += h,
h => process.OutputDataReceived -= h)
.Select(e => e.EventArgs.Data);
return Observable.Create<string>(observer =>
{
var cancel = Disposable.Create(process.CancelOutputRead);
return new CompositeDisposable(cancel, receivedStdOut.Subscribe(observer));
});
}
}
公共静态类RxProcessUtilities
{
///
///为流程创建可连接的可观察对象。
///
///必须是可连接的可观察对象才能阻止多个
///订阅以多次调用该进程。
///这个过程。
///
公共静态IConnectableObservable CreateConnectableObservableProcess
(字符串文件名、字符串参数、IObservable输入=null)
{
var可观测=可观测。使用(()=>
{
流程=新流程();
//进程配置
process.StartInfo.FileName=文件名;
process.StartInfo.Arguments=参数;
process.StartInfo.CreateNoWindow=true;
process.StartInfo.UseShellExecute=false;
process.EnableRaisingEvents=true;
process.StartInfo.RedirectStandardError=true;
process.StartInfo.RedirectStandardOutput=true;
如果(null!=输入)
{
process.StartInfo.RedirectStandardInput=true;
输入。订阅(s=>
{
如果(!process.HasExited)
{
过程。标准输入。写入;
}
});
}
返回过程;
},
进程=>
{
返回可观察的。创建(
(IObserver观察员)=>
{
//听stdout和stderr
var stdOut=RxProcessUtilities.CreateStandardOutputObservable(进程);
var stdErr=RxProcessUtilities.CreateStandardErrorObservable(流程);
var stdOutSubscription=stdOut.Subscribe(观察者);
var stdErrSubscription=stdErr.Subscribe(观察者);
var processExited=Observable.FromEventPattern
(h=>process.Exited+=h,h=>process.Exited-=h);
var processError=processExited.Subscribe(args=>
{
//这是我的问题:进程在所有事件之前发送已退出的事件*
//*已引发DataReceived事件
//在stdout和stderr缓冲区为空之前,我的丑陋的进程退出解决方案。
《睡眠》(2000年);
//另外:AFAICS我们无法同步读取缓冲区中剩余的内容,
//因为我们异步启动。这将引发:
//字符串restofsdout=process.StandardOutput.ReadToEnd();
//字符串restofsterr=process.StandardError.ReadToEnd();
如果(process.ExitCode!=0)
{
observer.OnError(新的例外情况
(String.Format(“进程{0}以错误代码{1}终止”),
process.StartInfo.FileName,process.ExitCode));
}
其他的
{
observer.OnCompleted();
}
});
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
返回新的CompositeDisposable
(stdOutSubscription,
标准订阅,
处理错误);
});
});
返回observable.Publish();
}
///
///为进程的标准错误创建IObservablestring。
///
///这个过程。
///
公共静态IObservable CreateStandardErrorObservable(流程)
{
//var processExited=Observable.FromEventPattern
//(h=>process.Exited+=h,h=>process.Exited-=h);
var接收数据记录器=
可观察的。从事件模式
(h=>process.ErrorDataReceived+=h,
h=>process.ErrorDataReceived-=h)
//.TakeUntil(进程退出)
//无法在此处使用,因为可能引发进程退出事件
//在所有stderr和stdout事件发生之前。
.Select(e=>e.EventArgs.Data);
返回可观察的。创建(观察者=>
{
var cancel=一次性的.Create(process.CancelErrorRead);
返回新的CompositeDisposable(取消,ReceivedDerr.Subscribe(观察者));
});
}
///
///为进程的标准输出创建IObservablestring。
///
///这个过程。
///
公共静态IObservable CreateStandardOutputObservable(进程)
{
收到的风险值=
可观察的。从事件模式
(h=>process.OutputDataReceived+=h,
h=>process.OutputDataReceived-=h)
.Select(e=>e.EventArgs.Data);
返回可观察的。创建(观察者=>
{
var cancel=Disposable.Create(process.CancelOut
process.WaitForExit();
/// <summary>
/// Creates a connectable observable for a process.
/// </summary>
/// <remarks>Must be a connectable observable in order to hinder multiple subscriptions to call the process multiple times.</remarks>
/// <param name="process">The process.</param>
/// <returns></returns>
public static IConnectableObservable<string> CreateConnectableObservableProcess(string filename, string arguments, IObservable<string> input = null)
{
var observable = Observable.Using(() =>
{
Process process = new Process();
// process configuration
process.StartInfo.FileName = filename;
process.StartInfo.Arguments = arguments;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.UseShellExecute = false;
process.EnableRaisingEvents = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.RedirectStandardOutput = true;
if (null != input)
{
process.StartInfo.RedirectStandardInput = true;
input.Subscribe(s =>
{
if (!process.HasExited)
{
process.StandardInput.Write(s);
}
});
}
return process;
},
process =>
{
return Observable.Create<string>(
(IObserver<string> observer) =>
{
// listen to stdout and stderr
var stdOut = RxProcessUtilities.CreateStandardOutputObservable(process);
var stdErr = RxProcessUtilities.CreateStandardErrorObservable(process);
var stdOutSubscription = stdOut.Subscribe(observer);
var stdErrSubscription = stdErr.Subscribe(observer);
var processExited = Observable.FromEventPattern(h => process.Exited += h, h => process.Exited -= h);
var processError = processExited.Subscribe(args =>
{
process.WaitForExit();
try
{
if (process.ExitCode != 0)
{
observer.OnError(new Exception(String.Format("Process '{0}' terminated with error code {1}",
process.StartInfo.FileName, process.ExitCode)));
}
else
{
observer.OnCompleted();
}
}
finally
{
process.Close();
}
});
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
return new CompositeDisposable(stdOutSubscription,
stdErrSubscription,
processError);
});
});
return observable.Publish();
}