对象是否有可能“固定”自己并避免垃圾回收?

时间:2010-03-16 02:33:45

标签: c# .net garbage-collection pinvoke

我正在尝试使用System.Media.SoundPlayer API编写waveOut...的替代品。当传递的文件/流完成播放时,此API会对我的SoundPlayer版本中的方法进行回调。

如果我创建一个表单范围的SoundPlayer实例并播放某些内容,一切正常,因为该表单使对象保持活动状态,因此委托处于活动状态以接收回调。

如果我在这样使用它,比如按钮点击事件:

SoundPlayer player = new SoundPlayer(@"C:\whatever.wav");
player.Play();

...它在99%的情况下工作正常,但偶尔(并且经常在文件很长的情况下)SoundPlayer对象在文件完成之前被垃圾收集,因此委托不再接收回调,我得到一个丑陋的错误。

我知道如何使用GCHandle.Alloc“固定”对象,但只有当其他东西可以挂在句柄上时。对象是否有任何方法可以在内部固定自身,然后在一段时间(或完成播放)后取消固定?如果我尝试GCHandle.Alloc (this, GCHandleType.Pinned);,我会得到一个运行时异常“对象包含非原始或非blittable数据。”

6 个答案:

答案 0 :(得分:7)

您可以拥有所有“当前播放”声音的static集合,只需在获得“完成播放”通知时移除SoundPlayer实例即可。像这样:

class SoundPlayer
{
    private static List<SoundPlayer> playing = new List<SoundPlayer>();

    public void Play(...)
    {
        ...
        playing.Add(this);
    }

    // assuming this is your callback when playing has finished
    public void OnPlayingFinished(...)
    {
        ...
        playing.Remove(this);
    }
}

(显然需要锁定/多线程,错误检查等)

答案 1 :(得分:2)

您的SoundPlayer对象应该只存储在表单类的私有字段中,以便保持足够长的引用。您可能需要在表单关闭时处理它。

Fwiw,pinning不起作用,因为您的类缺少[StructLayout]属性。并不是说它可以有效地使用它,你必须将返回的GCHandle存储在某个地方,这样你以后可以取消它。您的表单类是存储它的唯一合理位置。简单。

答案 2 :(得分:1)

执行此操作的最佳方法是在私有静态字段中保留[ThreadStatic]个活动SoundPlayer列表,并在声音完成时从列表中删除每个实例。

例如:

[ThreadStatic]
static List<SoundPlayer> activePlayers;

public void Play() {
    if(activePlayers == null) activePlayers = new List<SoundPlayer>();
    activePlayers.Add(this);
    //Start playing the sound
}
void OnSoundFinished() {
    activePlayers.Remove(this);
}

答案 3 :(得分:1)

GCHandle是要走的路;只是不要指定Pinned枚举值。

类静态成员(例如shown here)的问题是,如果程序的被管部分不再引用它们,则可能过早收集对象 。引用的示例显示使用回调“OnPlayingFinished”,但现在您必须担心保持委托(引用OnPlayingFinished的委托)自己被垃圾收集。

您仍然需要注册OnPlayingFinished,并让代表保持活着状态。但是,GCHandle会保持您的对象存活,因此您可以使用以下代码保持代理:

class SoundPlayer
{

    public void Play(...)
    {
        var h = GCHandle.Alloc(this);
        SomeNativeAPI.Play(this, h.ToIntPtr());
    }

    // assuming this is your callback when playing has finished
    delegate void FinishedCallback(IntPtr userData);
    static FinishedCallback finishedCallback = OnPlayingFinished;
    public static void OnPlayingFinished(IntPtr userData)
    {
        var h = GCHandle.FromIntPtr(userData);
        SoundPlayer This = (SoundPlayer)h.Target;
        h.Free();

        ... // use 'This' as your object
    }
}

我们确保通过GCHandle可以访问我们的SoundPlayer。由于SoundPlayer的实例仍然可以访问,因此其静态成员也必须保持可访问状态。

至少,这是我最好的教育猜测,你可以如何去做。

答案 4 :(得分:0)

这可能听起来太简单了,但只是强烈引用您自己班级中的SoundPlayer对象。只要对象还活着,就应该让GC离开。

即。而不是:

public class YourProgram
{
    void Play()
    {
       SoundPlayer player = new SoundPlayer(@"c:\whatever.wac");
       player.Play();
    }
}

此:

public class YourProgram
{
    private SoundPlayer player;
    void Play()
    {
       player = new SoundPlayer(@"c:\whatever.wac");
       player.Play();
    }
}

答案 5 :(得分:0)

您是否只是想阻止您的对象被垃圾收集?你不能打电话给GC.KeepAlive( this )保护它免受GC的侵害吗?