HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Creating multiple threads to process items in a listview

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
processcreatingthreadsitemsmultiplelistview

Problem

The goal is to pick items from a listview, do some web related task with the information and then update the UI with the returned information from the web request.

The problem is with having multiple list view items processed at once, me being a newbie to C#, i came up with this code, I'm pretty sure this is not good, i just came up with it, is there a better way to get this done ?

```
Dictionary worker_list = new Dictionary();
int completed_count = 0;
int total_work;

public Form1()
{
InitializeComponent();
}

public void worker(String woker_id)
{
WebClient client = new WebClient();
byte[] response =
client.UploadValues("http://google.com", new NameValueCollection() { });

worker_list[woker_id].ReportProgress(100);

if(completed_count != total_work){
worker(woker_id);
}
}

// Adding the work to the que since a thread can't access background Ui thread
private void button2_Click(object sender, EventArgs e)
{
// preparing task list
txtOutput.Text += "Total stuff to run" + listView1.Items.Count + Environment.NewLine + Environment.NewLine;

// check for valid input
int value;
if (!int.TryParse(textBox1.Text, out value) || textBox1.Text == "0"){
MessageBox.Show("invalid number of threads");
return;
}

// create threads
for (int i = 0; i < value; i++)
{
String woker_id = "bg_" + i.ToString();

worker_list[woker_id] = new BackgroundWorker();
worker_list[woker_id].WorkerSupportsCancellation = true;
worker_list[woker_id].WorkerReportsProgress = true;

// Intiial message
txtOutput.Text += "Worker " + woker_id + " Started" + Environment.NewLine;

worker_list[woker_id].DoWork += delegate {
worker(woker_id);
};

worker_list[woker_id].ProgressChanged += delegate {
if(completed_count == total_work) {
return;
}else
{
txtOutput.Text += "Worker "

Solution

We actually don't use the BackgroundWorker anymore. The same thing can be done much easier with the newer Task Parallel Library (TPL).

Briefly you need to (ok, you should) put the download logic into a new class that could have a Download method. It needs to be marked with async and return a Task to be awaitable. But you need a result so you can use it with a generic parameter Task. (Sidenote: remember to dispose the WebClient). For the new download to work you need to use the awaitable upload API.

If you want to be able to report progress or just be notified when a task is finished you can use the IProgress interface as one of the parameters. You call it inside the method to report its progress. Here just an arbitrary task-id.

class Downloader
{
    public async Task Download(int taskId, NameValueCollection data, IProgress progress)
    {
        using (var client = new WebClient())
        {
            var response = await client.UploadValuesTaskAsync("http://google.com", data);
            progress.Report(taskId);
            return response;
        }
    }
}


Your form code could then be reduced to a simple event handler that is also marked with async. It prepares a collection of download-tasks and then awaits them all or processes each one when it's finished (your choice).

When a task is finished, the form receives a progress report (that can be seen here as a notification) via the ProgressChanged event of an instance of the Progress class.

class MyForm : Form
{
    private readonly Downloader _downloader = new Downloader();

    private async void button2_Click(object sender, EventArgs e)
    {
        // Initialize progress handling.
        var progress = new Progress();
        progress.ProgressChanged += (downloader, taskId) => 
        {
            // update label...
        };

        var downloadCount = 2; //... get the count
        var downloadTasks = 
            Enumerable
            .Range(0, downloadCount)
            // Create task specific arguments.
            .Select((x, i) => _downloader.Download(i, new NameValueCollection(), progress));

        var results = await Task.WhenAll(downloadTasks);
    }
}


You may also use a different pattern if you want to process each task result right away by doing it as soon as any task is finished with WhenAny and a loop:

for (int i = 0; i < downloadCount; i++)
{
    var firstTask = await Task.WhenAny(downloadTasks);
    var result = await firstTask;
    // process task result...
}

Code Snippets

class Downloader
{
    public async Task<byte[]> Download(int taskId, NameValueCollection data, IProgress<int> progress)
    {
        using (var client = new WebClient())
        {
            var response = await client.UploadValuesTaskAsync("http://google.com", data);
            progress.Report(taskId);
            return response;
        }
    }
}
class MyForm : Form
{
    private readonly Downloader _downloader = new Downloader();

    private async void button2_Click(object sender, EventArgs e)
    {
        // Initialize progress handling.
        var progress = new Progress<int>();
        progress.ProgressChanged += (downloader, taskId) => 
        {
            // update label...
        };

        var downloadCount = 2; //... get the count
        var downloadTasks = 
            Enumerable
            .Range(0, downloadCount)
            // Create task specific arguments.
            .Select((x, i) => _downloader.Download(i, new NameValueCollection(), progress));

        var results = await Task.WhenAll(downloadTasks);
    }
}
for (int i = 0; i < downloadCount; i++)
{
    var firstTask = await Task.WhenAny(downloadTasks);
    var result = await firstTask;
    // process task result...
}

Context

StackExchange Code Review Q#151140, answer score: 8

Revisions (0)

No revisions yet.