Search code examples
c#queuefilecopy

Queue File Copy Operation using C# Queue<MethodInvoker>


I'm trying to create a file copy application. I have a BackgroudWorker doing the work and it works fine so far. How it works, I have a form, Source and Destination Folder fields and a Copy button. The Copy button triggers the Copy operation using the BackgroundWorker. The Progressbar gets updated etc. Now I need to implemet a queue type of operation. I need to add other Source and Destination Copy operation and add it to the queue. I tried using the following:

Queue<MethodInvoker> MyQueue = new Queue<MethodInvoker>();

        MyQueue.Enqueue(new MethodInvoker(() =>CopyStuff(1)));
        MyQueue.Enqueue(new MethodInvoker(() =>CopyStuff(2)));

        MethodInvoker bb = MyQueue.Dequeue(); //(I Forgot this part)
        bb();
        bb = MyQueue.Dequeue();
        bb();

        
        

The problem is, because it's a BackgroundWorker, it does not wait for the first operation to complete. Any suggestions on how to go about this? After fixing my code it works, except for it running on the UI thread, locking controls.

Update 1:

This works, but runs on main thread, user can't use controls while it's running:

        BlockingCollection<MethodInvoker> bCollection = new BlockingCollection<MethodInvoker>(boundedCapacity: 2);
        Task producerThread = Task.Factory.StartNew(() =>
        {
            for (int i = 0; i < 2; ++i)
            {

                bCollection.Add(CopyStuff);
            }

            bCollection.CompleteAdding();
        });

        foreach (MethodInvoker item in bCollection.GetConsumingEnumerable())
        {
            BeginInvoke(item);
        }

Update 2:

Works in a Console App, but not a Windows Forms Application.

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace AsyncCopy
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {

        }

        public class AsyncCopy
        {
            private static Queue<Action> MyQueue = new Queue<Action>();

            public async Task EnqueueAndCopy(Action[] actionList)
            {
                foreach (var action in actionList)
                {
                    MyQueue.Enqueue(action);
                }
                while (MyQueue.TryDequeue(out var copyAction)) //Here's the problem on Windows Form Applivcation
                {
                    //If the copyaction is itself async, await directly on the method here instead of running the action in a Task
                    await Task.Factory.StartNew(copyAction);
                }
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            var asyncCopy = new AsyncCopy();
            //In a typical usage, this will call be awaited
            //In your case, make this call from a async method and call it from the UI thread
            asyncCopy.EnqueueAndCopy(
                new Action[] {
                () => CopyStuff (1),
                () => CopyStuff (2)
                }
            );

            //Loop to confirm the processing is asynchronous
            for (int i = 0; i <= 20; i++)
            {
                Console.WriteLine($"{i}");
                Thread.Sleep(300);
            }
        }
        //Copy process simulation
        static void CopyStuff(int i)
        {
            Console.WriteLine($"Copying {i}");
            Thread.Sleep(3000);
            Console.WriteLine($"Copied {i}");
        }
    }
}

Solution

  • If you need to Queue the Copy process asynchronously as the other copies are processing, I would recommend a producer consumer pattern. Refer https://www.dotnetcurry.com/patterns-practices/1407/producer-consumer-pattern-dotnet-csharp

    But a simple async await would work in your case as well

       using System;
       using System.Collections.Generic;
       using System.Threading.Tasks;
       using System.Threading;
    
       namespace stackoverflow {
       class Program {
    
        static void Main (string[] args) {
            var asyncCopy = new AsyncCopy ();
            //In a typical usage, this will call be awaited
            //In your case, make this call from a async method and call it from the UI thread
            asyncCopy.EnqueueAndCopy (
                new Action[] {
                    () => CopyStuff (1),
                    () => CopyStuff (2)
                }
            );
    
            //Loop to confirm the processing is asynchronous
            for(int i=0; i <= 20; i++)
            {
                Console.WriteLine($"{i}");
                Thread.Sleep(300);
            }
        }
        //Copy process simulation
        static void CopyStuff (int i) {
            Console.WriteLine ($"Copying {i}");
            Thread.Sleep(3000);
            Console.WriteLine ($"Copied {i}");
        }
    
    }
    
    public class AsyncCopy {
        private static Queue<Action> MyQueue = new Queue<Action> ();
    
        public async Task EnqueueAndCopy (Action[] actionList) {
            foreach (var action in actionList) {
                MyQueue.Enqueue (action);
            }
            while (MyQueue.TryDequeue (out var copyAction)) {
                //If the copyaction is itself async, await directly on the method here instead of running the action in a Task
                await Task.Factory.StartNew (copyAction);
            }
        }
    }
    }
    

    Update

      using System;
      using System.Collections.Generic;
      using System.Threading;
      using System.Threading.Tasks;
      using System.Windows.Forms;
    
      namespace WindowsFormsApplication1
      {
       public partial class Form1 : Form
       {
        public Form1()
        {
            InitializeComponent();
        }
    
        private async Task CopyAsync(IEnumerable<Action> actionList)
        {
            foreach (var action in actionList)
            {
                await Task.Factory.StartNew(action);
            }
        }
    
        private async void button2_Click(object sender, EventArgs e)
        {
            await CopyAsync(
                new Action[]
                {
                    () => CopyStuff(1),
                    () => CopyStuff(2)
                });
        }
    
        //Copy process simulation
        static void CopyStuff(int i)
        {
            Thread.Sleep(3000);
            MessageBox.Show(string.Format("File Copied {0}", i));
        }
    }
    }