Возможно ли для объекта «закрепить» себя и избежать сбора мусора? - PullRequest
2 голосов
/ 16 марта 2010

Я пытаюсь написать замену для System.Media.SoundPlayer с использованием waveOut... API. Этот API выполняет обратный вызов метода в моей версии SoundPlayer, когда переданный файл / поток завершил воспроизведение.

Если я создаю экземпляр своего SoundPlayer в области формы и что-то играю, все работает нормально, потому что форма поддерживает живой объект, поэтому делегат жив, чтобы получить обратный вызов.

Если я использую это, например, в случае нажатия кнопки:

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

... он работает нормально в 99% случаев, но иногда (и часто, если файл длинный) объект SoundPlayer собирается сборщиком мусора до его завершения, поэтому делегат больше не может принимать обратный вызов, и я получаю ужасную ошибку.

Я знаю, как «закреплять» объекты, используя GCHandle.Alloc, но только когда что-то еще может висеть на ручке. Есть ли какой-нибудь способ для объекта закрепить себя внутри, а затем открепить себя после определенного периода времени (или завершения воспроизведения)? Если я попытаюсь GCHandle.Alloc (this, GCHandleType.Pinned);, я получу исключение во время выполнения «Объект содержит не примитивные или неблизкие данные».

Ответы [ 6 ]

7 голосов
/ 16 марта 2010

Вы можете просто собрать 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);
    }
}

(требуется блокировка / многопоточность, проверка ошибок и т. Д.)

2 голосов
/ 16 марта 2010

Ваш объект SoundPlayer должен просто храниться в закрытом поле вашего класса формы, чтобы на него ссылались достаточно долго. Вам, вероятно, нужно утилизировать его, когда ваша форма закрывается.

Кстати, закрепление не работает, потому что в вашем классе отсутствует атрибут [StructLayout]. Не то чтобы он работал эффективно с одним, вам нужно будет где-то хранить возвращенный GCHandle, чтобы потом можно было открепить его Ваш класс формы является единственным логичным местом для его хранения. Сделай это просто.

1 голос
/ 28 октября 2011

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 остается достижимым, его статические члены также должны оставаться доступными.

По крайней мере, это мое лучшее образованное предположение о том, как вы можете это сделать.

1 голос
/ 16 марта 2010

Лучший способ сделать это - сохранить список [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);
}
0 голосов
/ 16 марта 2010

Вы просто пытаетесь предотвратить сбор мусора на вашем объекте? Не могли бы вы позвонить GC.KeepAlive( this ), чтобы защитить его от GC?

0 голосов
/ 16 марта 2010

Это может показаться слишком простым, но просто сделайте сильную ссылку на объект 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();
    }
}
...