0

I have a method which consists of two lists (1. items to search and 2. workers to search with). Each worker takes an item from the list, searches for it, and add the results to a global results list which update the UI thread (a listview).

This is what I came up with so far:

List<Result> allResults = new List<Result>();
var search = new Search(workers);

//Will be full with items to search for
var items= new ConcurrentBag<item>();

while (items.Any())
{ 
    foreach (var worker in workers)
    {
        if (!items.Any())
            break;

        IEnumerable<Result> results = null;

        Task.Factory.StartNew(() =>
        {
            if (ct.IsCancellationRequested)
                return;

            items.TryTake(out Item item);
            if (item == null)
                return;

            results= search.DoWork(worker, item);
        }, ct);

        if (results?.Any() ?? false)
        {
            allResults.AddRange(reults);
        }

        //Update UI thread here?
    }
}

The workers should search in parallel and their results added to the global results list. This list will then refresh the UI.

Am I on the right track with the above approach? Will the workers run in parallel? Should I update the UI thread within the task and use BeginInvoke?

Ivan-Mark Debono
  • 15,500
  • 29
  • 132
  • 263
  • It seems like you are trying to allocate the items against a series of workers. Is that right? You're trying to process the flat list of items in parallel? – Enigmativity Oct 03 '18 at 02:50
  • @Enigmativity Yes. The workers will take an item for the list and process it asynchronously. This is done till the list is empty. – Ivan-Mark Debono Oct 03 '18 at 02:51

2 Answers2

2

This will run parallel searches from the list items up to a specified number of workers without blocking the UI thread and then put the results into a list view.

    private CancellationTokenSource _cts;

    private async void btnSearch_Click(object sender, EventArgs e)
    {
        btnSearch.Enabled = false;
        lvSearchResults.Clear();
        _cts = new CancellationTokenSource();
        AddResults(await Task.Run(() => RunSearch(GetItems(), GetWorkerCount(), _cts.Token)));
        btnSearch.Enabled = true;
    }

    private void btnCancel_Click(object sender, EventArgs e)
    {
        _cts?.Cancel();
    }

    private List<Result> RunSearch(List<Item> items, int workerCount, CancellationToken ct)
    {
        ConcurrentBag<List<Result>> allResults = new ConcurrentBag<List<Result>>();

        try
        {
            Parallel.ForEach(items, new ParallelOptions() { MaxDegreeOfParallelism = workerCount, CancellationToken = ct }, (item) =>
            {
                Search search = new Search(); // you could instanciate this elseware as long as it's thread safe...
                List<Result> results = search.DoWork(item);
                allResults.Add(results);
            });
        }
        catch (OperationCanceledException)
        { }

        return allResults.SelectMany(r => r).ToList();
    }

    private void AddResults(List<Result> results)
    {
        if (results.Count > 0)
            lvSearchResults.Items.AddRange(results.Select(r => new ListViewItem(r.ToString())).ToArray());
    }
C. Mitchell
  • 176
  • 7
-2

If your are working with Windows form, you can refer to How do I update the GUI from another thread?
If you are working with WPF. You can find your UI Dispatcher and use the dispatcher to update UI. Usually, even you try to update UI in a loop, it may not update the UI immediately. If you want to force to update UI, you can use DoEvents() method. The DoEvents() method also works for WPF. But try to avoid using DoEvents().

wuyin lyu
  • 31
  • 3
  • Arggh - never ever call `DoEvents()`. It's only in the framework for VB6 compatibility. It circumvents the message loop and can cause nasty re-entrancy bugs. – Enigmativity Oct 03 '18 at 06:08