C# 可观察。使用TimeSpan选择器生成时出现内存泄漏[使用TimeSpan时>15ms]
我正在调查Observable.Generate的使用情况,以创建一系列每隔一段时间采样的结果,使用msdn网站上的示例作为起点 不带TimeSpan选择器的以下代码不会出现内存泄漏:C# 可观察。使用TimeSpan选择器生成时出现内存泄漏[使用TimeSpan时>15ms],c#,system.reactive,C#,System.reactive,我正在调查Observable.Generate的使用情况,以创建一系列每隔一段时间采样的结果,使用msdn网站上的示例作为起点 不带TimeSpan选择器的以下代码不会出现内存泄漏: IObservable<string> obs = Observable.Generate(initialState: 1, condition: x => x < 1000,
IObservable<string> obs = Observable.Generate(initialState: 1,
condition: x => x < 1000,
iterate: x => x + 1,
resultSelector: x => x.ToString());
obs.Subscribe(x => Console.WriteLine(x));
TimeSpan timeSpan = TimeSpan.FromSeconds(1);
IObservable<string> obs = Observable.Generate(initialState: 1,
condition: x => x < 1000,
iterate: x => x + 1,
resultSelector: x => x.ToString(),
timeSelector: x => timeSpan);
obs.Subscribe(x => Console.WriteLine(x));
我是否错误地使用了此API?
我应该期望内存增长,还是这是一个反应性错误?这在我看来确实是一个错误,或者至少在DefaultScheduler的“递归”调度实现中是一个混乱/不受欢迎的行为(这不是真正的递归,我指的是在调度程序中传递给预定操作的重载,这样您就可以安排继续) 您看到的可处置组件是通过调用DefaultScheduler.Schedule方法(此处第71行:)创建的 这里的其他尝试之所以失败,有几个原因。首先,一次性物品最终会被处置——但只有当Generate
OnComplete
或OnErrors
完成时,才会对Generate在订阅时返回的System.Reactive.AnonymousSafeObserver
进行清理
其次,如果您使用较短的TimeSpan
(记住.NET计时器的最小分辨率无论如何都是15ms),那么Rx将优化计时器的使用,并在不使用计时器的情况下调用QueueUserWorkItem
,这样就不会创建这些一次性物品
如果深入研究Generate的实现()您可以看到,它将初始调用返回的IDisposable
传递给Schedule,并将其传递回观测者,观测者将一直保留到出错/完成为止。这防止了整个递归调用的结果链被收集,这意味着如果您确实需要取消,或者当清理发生时,每个Schedule都将被收集led action的一次性设备无法处理
您可以在下面直接使用DefaultScheduler的代码中看到相同的效果-最后一行中对cancel
的引用足以导致泄漏。请确保使用发布版本,否则编译器将保留cancel直到方法结束
// ensure you are using a release build of this code
ManualResetEvent mre = new ManualResetEvent();
IDisposable cancel;
int maxCount = 20;
TimeSpan timeSpan = TimeSpan.FromSeconds(1);
Func<IScheduler, int, IDisposable> recurse = null;
recurse = (self, state) =>
{
Console.WriteLine(state);
if (state == maxCount)
{
mre.Set();
return Disposable.Empty;
}
return self.Schedule(state + 1, timeSpan, recurse);
};
cancel = Scheduler.Default.Schedule(1, timeSpan, recurse);
mre.WaitOne();
// uncomment the following line, and you'll get the same leak
// leave it commented, and cancel reference is GC'd early and there's no leak
// if(cancel == null) Console.WriteLine("Hang on to cancel");
//确保您正在使用此代码的发布版本
ManualResetEvent mre=新的ManualResetEvent();
IDisposable取消;
int maxCount=20;
TimeSpan TimeSpan=TimeSpan.FromSeconds(1);
Func recurse=null;
递归=(自身,状态)=>
{
控制台写入线(状态);
如果(状态==maxCount)
{
mre.Set();
返回一次性。空;
}
返回self.Schedule(状态+1,时间跨度,递归);
};
cancel=Scheduler.Default.Schedule(1,timeSpan,recurse);
韦通先生();
//取消对以下行的注释,您将得到相同的泄漏
//保留注释,取消引用是GC的早期版本,并且没有泄漏
//if(cancel==null)Console.WriteLine(“挂起以取消”);
我使用Jetbrains dotMemory API进行内存转储以得出结论-我已经将上面的代码从这些API调用中剥离出来,但是如果您有该产品,这里有一个完整的要点,您将能够非常清楚地看到取消注释最后一行的影响:或者,您可以使用MS profiler API-我没有提到过到目前为止我大脑的工作区!可能正在进行多个订阅。
是否可以在事件处理程序中观察到?
上的投票数令人费解,因为任何人都可以复制粘贴并运行此代码,并看到没有内存泄漏…)@supertopi取决于timeSpan
的声明位置。>我是否错误地使用了此API?那么您是如何使用API的呢?此处的一条注释表明内存泄漏取决于您在何处声明TimeSpan。在这种情况下,您需要详细说明,以便其他人可以实际复制您的问题。我用一个简单的程序示例更新了这个问题。感谢您的任何想法和建议!非常感谢大家。“我还没有把它输入到我的大脑工作区”,从现在起我将使用这个短语。随着时间的流逝,可能会更频繁!事实上,你能帮我解决这个问题吗?我不知怎的被卡住了,虽然这应该很简单。
System.Reactive.Disposables StableCompositeDisposable.Binary
System.Reactive.Disposables SingleAssignmentDisposable
// ensure you are using a release build of this code
ManualResetEvent mre = new ManualResetEvent();
IDisposable cancel;
int maxCount = 20;
TimeSpan timeSpan = TimeSpan.FromSeconds(1);
Func<IScheduler, int, IDisposable> recurse = null;
recurse = (self, state) =>
{
Console.WriteLine(state);
if (state == maxCount)
{
mre.Set();
return Disposable.Empty;
}
return self.Schedule(state + 1, timeSpan, recurse);
};
cancel = Scheduler.Default.Schedule(1, timeSpan, recurse);
mre.WaitOne();
// uncomment the following line, and you'll get the same leak
// leave it commented, and cancel reference is GC'd early and there's no leak
// if(cancel == null) Console.WriteLine("Hang on to cancel");