-2

I copy files in a Zip archive (3td part library to get the progress) with a BackgroundWorker so I not block the UI and I can upgrade the ProgressBar or cancel the copy.

This is the code of BackgroundWorker, source, target and name are general variable:

private void _backgroundWorkerB1_DoWork(object sender, DoWorkEventArgs e)
{
    try
    {
        //Check if the source directory exist
        if (Directory.Exists(source) == false)
        {
            e.Result = "NotExist";
            return;
        }

        //If the target not exist I will create it
        if (Directory.Exists(target) == false)
        {
            Directory.CreateDirectory(target);
        }

        string filename = target + "\\" +
            DateTime.Now.ToString("yyyy'-'MM'-'dd'_'HH'_'mm'_'ss") +
            "-" + name + ".zip";

        // 3td part library
        ArchivioB1.FileName = filename;

        ArchivioB1.OpenArchive(System.IO.FileMode.Create);

        ArchivioB1.OnOverallProgress += new BaseArchiver
            .OnOverallProgressDelegate(ArchivioB1_OnOverallProgress);

        // Source directory
        ArchivioB1.BaseDir = source;

        // Copy all files
        ArchivioB1.AddFiles("*.*");

        // Close
        ArchivioB1.CloseArchive();
    }
    catch (Exception ex)
    {
        e.Result = "Error";
        return;
    }
}

I need to modify the code to make it in parallel from a DataGrid. I will have a DataGrid on the first column the name of the file, on the second a progress bar, on third the State and on fourth the destination of file. The DataGrid can have an undefined rows and these rows can be more than cores of the CPU. How can I run the same function in parallel, and if the rows are more than the CPU cores, the system have to wait till one of it will be free and continue with the next copy?

I don't understand why the community have to close the question when isn't duplicate like they think, in any case if someone need to copy files in archive parallely with a progress Hereafter final Edit with working code:

private async void btBackup_Click(object sender, RoutedEventArgs e)
    {
        btBackup.IsEnabled = false; 
        btCancel.IsEnabled = true; 

        var row_list1 = GetDataGridRows(dgwStation);
        List<Task> tasks = new List<Task>();
        ZipForge[] ZipList = new ZipForge[DataGridItemsList.Count];

        tokenSource = new CancellationTokenSource();

        foreach (DataGridRow single_row in row_list1)
        {
            int riga = single_row.GetIndex();

            ZipList[riga] = new ZipForge();
            tasks.Add(Task.Run(() => CopyTest(riga,ZipList[riga])));
        }

        await Task.WhenAll(tasks);

        if (generalerror)
            tbkStato.Text = "Completed with error";
        else if (tbkStato.Text != "Cancelled")
            tbkStato.Text = "Completed";
    }
private void btCancel_Click(object sender, RoutedEventArgs e)
    {
        try
        {
            if (tokenSource != null) //se il token non è nullo, posso chiedere la cancellazione
            {
                //tbkStato.Text = "Cancelled";

                tokenSource.Cancel();
            }
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
        }
    }
public void CopyTest(int rowindex, ZipForge ArchivioB1)
    {
        string nome1 = "";
        try
        {
            //Takes data from DatGridItemsList
            string source = DataGridItemsList[rowindex].Source, target = DataGridItemsList[rowindex].Target, filename = DataGridItemsList[rowindex].FileName;

            tbkStato.Dispatcher.Invoke(new Action(() => { tbkStato.Text = "Check source"; }));

            if (Directory.Exists(source) == false)
            {
                DataGridItemsList[rowindex].State = "Not Exist";
                dgwStation.Dispatcher.Invoke(new Action(() => { dgwStation.Items.Refresh(); }));
                generalerror = true;
                return;
            }

            tbkStato.Dispatcher.Invoke(new Action(() => { tbkStato.Text = "Check target"; }));

            if (Directory.Exists(target) == false)
            {
                Directory.CreateDirectory(target);
            }

            DataGridItemsList[rowindex].State = "creating Zip";
            dgwStation.Dispatcher.Invoke(new Action(() => { dgwStation.Items.Refresh(); }));

            nome1 = target + "\\" + DateTime.Now.ToString("yyyy'-'MM'-'dd'_'HH'_'mm'_'ss") + "-" + filename + ".zip";

            ArchivioB1.FileName = nome1;

            ArchivioB1.OpenArchive(System.IO.FileMode.Create,FileAccess.Write);

            ArchivioB1.Comment = rowindex.ToString();

            ArchivioB1.OnOverallProgress += new BaseArchiver.OnOverallProgressDelegate(ArchivioB1_OnOverallProgress);

            ArchivioB1.BaseDir = source;

            // Copia tutti i file nell'archivio creato
            ArchivioB1.AddFiles("*.*");

            if (tokenSource.Token.IsCancellationRequested) //Interruzzione dell'utente
            {
                tokenSource.Token.ThrowIfCancellationRequested();
            }
            else
            {

                ArchivioB1.CloseArchive();

                DataGridItemsList[rowindex].State = "Completed";
                dgwStation.Dispatcher.Invoke(new Action(() => { dgwStation.Items.Refresh(); }));
            }
        }

        catch (OperationCanceledException)
        {
            ArchivioB1.CloseArchive();

            if (File.Exists(nome1))
                File.Delete(nome1);

            DataGridItemsList[rowindex].State = "Cancelled";
            dgwStation.Dispatcher.Invoke(new Action(() => { dgwStation.Items.Refresh(); }));
            tbkStato.Dispatcher.Invoke(new Action(() => { tbkStato.Text = "Cancelled"; }));
            return;
        }
        catch (Exception ex)
        {
            ArchivioB1.CloseArchive();

            if (File.Exists(nome1))
                File.Delete(nome1);

            DataGridItemsList[rowindex].State = ex.Message.ToString();
            dgwStation.Dispatcher.Invoke(new Action(() => { dgwStation.Items.Refresh(); }));
            generalerror = true;
            return;
        }

    }
private void ArchivioB1_OnOverallProgress(object sender, double progress, TimeSpan timeElapsed, TimeSpan timeLeft, ProcessOperation operation, ProgressPhase progressPhase, ref bool cancel)
    {
        if (tokenSource.Token.IsCancellationRequested) //Interruzzione dell'utente
        {
            cancel = true;
            //tokenSource.Token.ThrowIfCancellationRequested();
        }

        ZipForge Archivio = (ZipForge)sender;

        int indice = Convert.ToInt32(Archivio.Comment);

        DataGridItemsList[indice].Progress = Convert.ToInt32(progress);
        dgwStation.Dispatcher.Invoke(new Action(() => { dgwStation.Items.Refresh(); }));
    }
Paolo
  • 5
  • 3
  • You can also take a look to the [how can I limit parallel-foreach](https://stackoverflow.com/questions/9290498/how-can-i-limit-parallel-foreach) – Rekshino Jul 14 '21 at 11:44
  • @Clemens how is this question related with [that](https://stackoverflow.com/questions/68376363/multiple-parallel-undefined-backgroundworker "How to limit the amount of concurrent async I/O operations?") question? This question has nothing to do with asynchronous operations, and it's unlikely that the operations are exclusively I/O-bound either. Archiving files is probably quite CPU-bound as well. – Theodor Zoulias Jul 14 '21 at 11:53
  • Paolo could you include the `ArchivioB1_OnOverallProgress` event handler in the question? – Theodor Zoulias Jul 14 '21 at 11:58
  • So there is probably a `_backgroundWorkerB1_ProgressChanged` event handler too. Can you include it in the question? We have to see how and where you are currently updating the `DataGrid`, otherwise how can we help? – Theodor Zoulias Jul 14 '21 at 12:16
  • @TheodorZoulias In the `BackgroundWorker` I just updating an general `ProgressBar` and `TextBlock`. I need to modify the code to run it parallel for upgrade an `DataGrid` – Paolo Jul 14 '21 at 12:23
  • Paolo we might be able to help you run your code in parallel, if you show us how it is currently running sequentially. Currently the only advice you could get from this question is something like "try using the [`Parallel.ForEach`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.parallel.foreach)". – Theodor Zoulias Jul 14 '21 at 12:27
  • Currently your code consists of the three commands `.OpenArchive()`, `.AddFiles()` and `.CloseArchive()`, which is not parallelizable. To parallelize a workload, you must be able to partition it to multiple independent sub-workloads. This is not the case with the code you've shown us. – Theodor Zoulias Jul 14 '21 at 12:35
  • I copied on dropbox an example solution where with one button is execute the single copy with the `BackGroundWorker` and the other button with` DataGrid` + `Task`. The Task block the UI (why the same code in Background not?) and exectue just the first row. To download the solution -> https://www.dropbox.com/s/23o6blo4z5uwnh9/TestMultipleCopy.zip?dl=0 – Paolo Jul 14 '21 at 14:11
  • @TheodorZoulias regarding the working code in the question, do you see some probably bug? and if the CPU have 4 core but the copies are 8 the threadpool will be managed in automatic by the Task? – Paolo Jul 15 '21 at 10:26
  • Paolo a question is supposed to contain a question and not an answer. If you want your question to be reopened, so that [you can answer it](https://stackoverflow.com/help/self-answer) properly, there are instructions [here](https://stackoverflow.com/help/reopen-questions). There is no guarantee that your question will be reopened through, and honestly I don't think that it's likely, but in any case this is what you are supposed to do. – Theodor Zoulias Jul 15 '21 at 10:54

1 Answers1

0

I would recommend using Tasks instead of Backgroundworkers.You can run as many Tasks as you wish in a simple mannner using Task.WhenAll...

If you want to execute tasks syncronously just omit await...

public async ExecuteMultipleTasksSimultaneously()
{

List<Task> tasks = new List<Task>()
{
    Task.Factory.StartNew(() => ActionA()),
    Task.Factory.StartNew(() => ActionB()),
    Task.Factory.StartNew(() => ActionC())
};

//Execute all tasks "at the same time"...

await Task.WhenAll(tasks); 
 // or await Task.WhenAny(tasks);  
// or await Task.WaitAll(tasks) 
//or Task.WaitAny(tasks)  depending on your execution needs 

// Continue on this thread... 

//Alternative to Task.Factory.StartNew(() => ActionA()) you could use 
// Task.Run(()=> ActionA())

}

I hope this points the right direction. Best regards.

A. Dzebo
  • 558
  • 6
  • 13
  • I need to start the same Action multiple times, I will just change the source and target because the code will do the same things. I edited my question where you can see the code that I use to test but the final result isn't what I expet – Paolo Jul 14 '21 at 12:27
  • Just provide different parameters to the action. You can also start the Tasks without waiting for result. Just call Task.Run(()=> SameAction(differentparameters). To Update UI from your background operations use. App.Current.Dispatcher.Invoke(() => UpdateUIFunction()); To many operations updating UI too frequently will impact (slow down) your UI. I would recommed that you Update the UI periodically at about 200 ms or more. BUT, for these kind of things I really recommend implementing MVVM pattern (Binding). Direct updating of UIcomponent's is not a good way to go for this type of things. – A. Dzebo Jul 15 '21 at 10:34