1

Trying to play .wav files in a thread safe queue. Calls may be triggered at random by any thread but must play in sequence. Problem is when I call Play(string[] files) with say 3 file names (currently testing from the UI thread), the last file is played 3 times, and the two first never played. Can't see what causes this..?

Would appreciate suggestion for easier way to do this, but mainly like to know why this doesn't work.

public class Audio
{
    static ActionQueue actionQueue;
    public static string MediaFolder { get; set; }
    private static object syncroot = new object();

    static Audio()
    {
        actionQueue = new ActionQueue();
    }

    /// <summary>
    /// Plays .wav in async queue. 
    /// </summary>
    /// <param name="fileName"></param>
    public static void Play(string fileName)
    { 
        actionQueue.Add(() => PlaySound(fileName));
    }

    public static void Play(string[] files)
    {
        Console.WriteLine("ID0: " + Thread.CurrentThread.ManagedThreadId);
        foreach (string f in files.ToList())
        {
            Console.WriteLine("Queue file: " + f);
            actionQueue.Add(() => PlaySound(f));
        }
    }

    private static void PlaySound(string f)
    {
        Console.WriteLine("ID1: " + Thread.CurrentThread.ManagedThreadId);

        var fileName = f;

        Console.WriteLine("Play queue: " + fileName);

        fileName = Path.Combine(MediaFolder, fileName);

        if (!Path.HasExtension(fileName))
            fileName = fileName + ".wav";

        if (!File.Exists(fileName)) return;
        string ext = Path.GetExtension(fileName);
        if (ext != ".wav") return;

        Console.WriteLine("Playing: " + fileName);

        SoundPlayer player = new SoundPlayer(fileName);
        player.PlaySync();
    }
}

public class ActionQueue
{
    private  BlockingCollection<Action> persisterQueue = new BlockingCollection<Action>();     

    public  ActionQueue( )
    { 
        var thread = new Thread(ProcessWorkQueue);
        thread.IsBackground = true;
        thread.Start();
    }

    private   void ProcessWorkQueue()
    {
        while (true)
        {
            var nextWork = persisterQueue.Take(); 
            nextWork();  
        }
    }

    public  void Add(Action action)
    { 
        persisterQueue.Add(action ); 
    }
}
bretddog
  • 5,411
  • 11
  • 63
  • 111

1 Answers1

3

It's the classic captured-loop-variable-in-a-closure problem.

You need to take a copy of your loop variables i.e.

    foreach (string f in files.ToList())
    {
        var copy = f;
        Console.WriteLine("Queue file: " + f);
        actionQueue.Add(() => PlaySound(copy));
    }

the reason being your delegates being passed to actionQueue are not executed until the loop has finished. By that time, of course, the variable f has changed value.

Community
  • 1
  • 1
Tim Rogers
  • 21,297
  • 6
  • 52
  • 68