物体是否有可能“钉住”物体?本身并避免垃圾收集?

发布于 2024-08-25 10:22:22 字数 626 浏览 8 评论 0原文

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

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

如果我像这样在按钮单击事件中使用它:

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

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

我知道如何使用 GCHandle.Alloc“固定”对象,但前提是其他东西可以挂在句柄上。有没有办法让对象在内部固定自己,然后在一段时间(或播放完成)后取消固定自己?如果我尝试 GCHandle.Alloc (this, GCHandleType.Pinned);,我会收到运行时异常“对象包含非原始或不可直接传输的数据”。

I'm trying to write a drop-in replacement for System.Media.SoundPlayer using the waveOut... API. This API makes a callback to a method in my version of SoundPlayer when the file/stream passed it has completed playback.

If I create a form-scoped instance of my SoundPlayer and play something, everything works fine because the form keeps the object alive, so the delegate is alive to receive the callback.

If I use it like this in, say, a button click event:

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

... it works fine 99% of the time, but occasionally (and frequently if the file is long) the SoundPlayer object is garbage-collected before the file completes, so the delegate is no longer there to receive the callback, and I get an ugly error.

I know how to "pin" objects using GCHandle.Alloc, but only when something else can hang onto the handle. Is there any way for an object to pin itself internally, and then un-pin itself after a period of time (or completion of playback)? If I try GCHandle.Alloc (this, GCHandleType.Pinned);, I get a run-time exception "Object contains non-primitive or non-blittable data."

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

野心澎湃 2024-09-01 10:22:22

您可以只拥有所有“当前播放”声音的静态集合,并在收到“播放完毕”通知时简单地删除 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);
    }
}

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

You could just have a static collection of all the "currently playing" sounds, and simply remove the SoundPlayer instance when it gets the "finished playing" notification. Like this:

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);
    }
}

(Obviously locking/multithreading, error checking and so on required)

油饼 2024-09-01 10:22:22

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

Fwiw,固定不起作用,因为您的类缺少 [StructLayout] 属性。并不是说它可以有效地工作,您必须将返回的 GCHandle 存储在某处,以便稍后可以取消固定它。您的表单类是存储它的唯一合乎逻辑的位置。让它变得简单。

Your SoundPlayer object should just be stored in a private field of your form class so that it stays referenced long enough. You probably need to dispose it when your form closes.

Fwiw, pinning doesn't work because your class is missing a [StructLayout] attribute. Not that it will work effectively with one, you would have to store the returned GCHandle somewhere so that you can unpin it later. Your form class is the only logical place to store it. Make it simple.

甲如呢乙后呢 2024-09-01 10:22:22

GCHandle 是最佳选择;只是不指定固定枚举值。

类静态成员的问题(例如 此处)表明,如果程序的托管部分不再引用对象,则对象可能会被过早收集。引用的示例显示使用回调“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
    }
}

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

至少,这是我对你如何去做的最好的猜测。

GCHandle is the way to go; just don't specify the Pinned enum value.

The problem with a class static member (such as shown here) is that the objects may be collected too early if the managed portion of the program no longer references them. The referenced example shows using a callback "OnPlayingFinished", but now you have to worry about keeping the delegate (the one that references OnPlayingFinished) from itself being garbage collected.

You will still need to register for OnPlayingFinished, and keep the delegate alive. However, the GCHandle is keeping your object alive, so you can keep the delegate around with:

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
    }
}

We've ensured our SoundPlayer remains reachable via the GCHandle. And as an instance of SoundPlayer remains reachable, its static members must also remain reachable.

At least, that's my best educated guess as to how you might go about it.

小矜持 2024-09-01 10:22:22

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

例如:

[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);
}

The best way to do this is to keep a [ThreadStatic] list of active SoundPlayers in a private static field, and remove each instance from the list when the sound finishes.

For example:

[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);
}
不如归去 2024-09-01 10:22:22

这听起来可能太简单了,但只需在您自己的类中对 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();
    }
}

This might sound too simple, but just make a strong reference to the SoundPlayer object in your own class. That ought to keep GC away as long as the object is alive.

I.e. instead of:

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

This:

public class YourProgram
{
    private SoundPlayer player;
    void Play()
    {
       player = new SoundPlayer(@"c:\whatever.wac");
       player.Play();
    }
}
-黛色若梦 2024-09-01 10:22:22

您只是想阻止您的对象被垃圾收集吗?您不能调用 GC.KeepAlive( this ) 来保护它免受 GC 的影响吗?

Are you simply trying to prevent your object from being garbage collected? Couldn't you call GC.KeepAlive( this ) to protect it from the GC?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文