Click here to Skip to main content
15,919,749 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
I have several csv files, function that convert file to DataTable and dataset to collect all tables. I have tried to run task asynchronously for each file and to increment value of ProgressBar when finished, but progressBar does not updates while all tasks finished. I would like to increment the value after each task finished. Could you please help me to solve the issue?

What I have tried:

List<string> files = new();
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{
    files.AddRange(openFileDialog1.FileNames);
}

Task<DataTable?>[] tasks = new Task<DataTable?>[files.Count];
for (int currentTask = 0; currentTask < files.Count; currentTask++)
{
    string file = files[currentTask];
    tasks[currentTask] = Task.Run(() =>
    {
        DataTable? tbl = CSVtoDataTable(file);
        progressBar1.BeginInvoke(() => progressBar1.Value++);
        return tbl;
    });

}
Task.WaitAll(tasks);

DataSet dts = new DataSet();

foreach (var task in tasks)
{
    dts.Tables.Add(task.Result);
}
Posted

Instead of Invoking your Progress bar, use an IProgress<T> Interface (System) | Microsoft Learn[^] to report the progress to the GUI thread via an event:
C#
private async void MyButton_Click(object sender, EventArgs e)
    {
    showProgress.Show();
    IProgress<int> progress = new Progress<int>(value => showProgress.Value = value);
    await Task.Run(() =>
    {
        for (int i = 0; i != 100; ++i)
            {
            progress.Report(i);
            Thread.Sleep(100);
            }
    });
    showProgress.Hide();
    }
 
Share this answer
 
v2
Comments
Bogomil Todorov 2024 17-Jan-24 5:35am    
Now I have tried the following, but still does not update progressbar:
IProgress<int> progress = new Progress<int>(value =>
{
progressBar1.Value = value;
});
Task<datatable?>[] tasks = new Task<datatable?>[files.Count];
for (int currentTask = 0; currentTask < files.Count; currentTask++)
{
string file = files[currentTask];
tasks[currentTask] = Task.Run(() =>
{
DataTable? tbl = CSVtoDataTable(file);
progress.Report(currentTask);
return tbl;
});

}
Task.WaitAll(tasks);
OriginalGriff 17-Jan-24 6:18am    
Of course it won't: each loop creates it's own Task which "picks up" it's value of currentTask when the handler code gets to execute - not the value of the variable when the Task.Run call was made. And since they all run independently the value of the progress bar is going to be set to "full" by the first task to complete it's job - and stay there for all the others.

Move the counter to the progress reporting code and it'll start to work.
Bogomil Todorov 2024 17-Jan-24 6:38am    
First thanks for the quick replies!
However, I still don't understand exactly how to do it. How to "Move the counter to the progress reporting code"?
OriginalGriff 17-Jan-24 6:47am    
You are interested in "how many tasks have completed so far?" not "what's the number of the task that just completed?" because you use that to set your ProgressBar value. So you want a variable that increments each time a task completes, not when you create them as some will take longer than others depending on how much data they are processing.
So create a class level counter, zero it before your loop, and increment it in the Progress handler method.

Sorry, but if you are going to use multithreading, you need to understand what is going on: it's not a "Magic Bullet" you can lob in without a good amount of thought: it's very, very easy to slow the whole task down instead of speeding it up if you aren't careful!
Bogomil Todorov 2024 17-Jan-24 7:08am    
I'm beginner in tasks. So far I have always used BackgroundWorkers, but was informed that it is better to replace them with tasks.
Could you please give me more details or example how to do "So create a class level counter, zero it before your loop, and increment it in the Progress handler method."?
Here is a different way of quickly processing the files using Task Parallel Library (TPL) - .NET | Microsoft Learn[^]:
C#
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    List<string> files = new();

    private async void button1_Click(object sender, EventArgs e)
    {
        string pathToFilesToProcess = @"c:\windows\system32";

        await ProcessFiles(pathToFilesToProcess).ConfigureAwait(false);
    }

    private async Task ProcessFiles(string pathToFilesToProcess)
    {
        string[] filesToProcess = Directory.GetFiles(pathToFilesToProcess);

        int progressCount = 0;

        progressBar1.Value = 0;
        progressBar1.Visible = true;
        progressBar1.Maximum = filesToProcess.Length;

        label1.Visible = true;

        IProgress<int> progress = new Progress<int>(value =>
        {
            // update the ProgressBar
            progressBar1.Value = value;
            // show file count
            label1.Text = progressCount.ToString("N0");
        });

        await Task.Run(() =>
        {
            Parallel.ForEach(
                filesToProcess,
                new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount - 1 },
                file =>
                {
                    Debug.WriteLine($"processing file: {file} on thread {Thread.CurrentThread.ManagedThreadId}");
                    Thread.Sleep(TimeSpan.FromMilliseconds(100));
                    progress.Report(Interlocked.Increment(ref progressCount));
                });
        });

        Debug.WriteLine($"Final Report on thread {Thread.CurrentThread.ManagedThreadId}");

        // not required as we are on thread 1 / UI thread. Example if it was ...
        label1.InvokeIfRequired(() => label1.Text = $@"{progressCount:N0} files processed.");

        // give the user time to see the completion...
        await Task.Delay(TimeSpan.FromSeconds(5));

        progressBar1.Visible = false;
        label1.Visible = false;
    }
}

public static class WinFormExtensions
{
    public static void InvokeIfRequired(this ISynchronizeInvoke obj, MethodInvoker action)
    {
        if (obj.InvokeRequired)
            obj.Invoke(action, null);
        else
            action();
    }
}
 
Share this answer
 
v2
Comments
Bogomil Todorov 2024 18-Jan-24 1:40am    
"A picture is worth a thousand words!"
With the given example it is now clearer to me how to use the tasks and progressBars.
Thank you very much!

I would recommend not to block any threads and to update the ProgressBar only on the UI thread. To update the ProgressBar, calculate the percentage of the total work that each file represents and update the bar by that value after each job is completed. Here's an example method:


C#
//useage: myDataSet= await DataSetBuilder( fileList, ProgressBar progressBar1)
private async Task<DataSet> DataSetBuilder(IEnumerable<string> stringCollection, ProgressBar progressBar)
{

    int percentagePerFile = 100 / stringCollection.Count();
    DataSet dts = new DataSet();
    foreach (string file in stringCollection)
    {

        DataTable tbl = await Task.Run<DataTable>(() =>
        {
          //Threadpool thread is running here
            return CSVtoDataTable(file);

        });
        //Back on the UI thread
        progressBar.Value += percentagePerFile;
        dts.Tables.Add(tbl);
    }
    return dts;
}
 
Share this answer
 
v2
Comments
Bogomil Todorov 2024 18-Jan-24 2:30am    
Thank you very much!
George Swan 18-Jan-24 2:46am    
You are most welcome.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900