Note that there are some explanatory texts on larger screens.

plurals
  1. POAccess List<T> elements in multiple threads and report progress
    primarykey
    data
    text
    <p>Inside my application I have list of persons from my database. For every person I must call 5 (for now) services to search for some informations. If service returns info I' adding it to that person (list of orders for specific person)<br> Because services work independent I thought I could try to run them parallel. I've created my code as so:</p> <pre><code>using System; using System.Collections.Generic; using System.Threading; namespace Testy { internal class Program { internal class Person { public int Id { get; set; } public string Name { get; set; } public List&lt;string&gt; Orders { get; private set; } public Person() { // thanks for tip @juharr Orders = new List&lt;string&gt;(); } public void AddOrder(string order) { lock (Orders) //access across threads { Orders.Add(order); } } } internal class Service { public int Id { get; private set; } public Service(int id) { Id = id; } //I get error when I use IList instead of List public void Search(ref List&lt;Person&gt; list) { foreach (Person p in list) { lock (p) //should I lock Person here? and like this??? { Search(p); } } } private void Search(Person p) { Thread.Sleep(50); p.AddOrder(string.Format("test order from {0,2}", Thread.CurrentThread.ManagedThreadId)); Thread.Sleep(100); } } private static void Main() { //here I load my services from external dll's var services = new List&lt;Service&gt;(); for (int i = 1; i &lt;= 5; i++) { services.Add(new Service(i)); } //sample data load from db var persons = new List&lt;Person&gt;(); for (int i = 1; i &lt;= 10; i++) { persons.Add( new Person {Id = i, Name = string.Format("Test {0}", i)}); } Console.WriteLine("Number of services: {0}", services.Count); Console.WriteLine("Number of persons: {0}", persons.Count); ManualResetEvent resetEvent = new ManualResetEvent(false); int toProcess = services.Count; foreach (Service service in services) { new Thread(() =&gt; { service.Search(ref persons); if (Interlocked.Decrement(ref toProcess) == 0) resetEvent.Set(); } ).Start(); } // Wait for workers. resetEvent.WaitOne(); foreach (Person p in persons) { Console.WriteLine("{0,2} Person name: {1}",p.Id,p.Name); if (null != p.Orders) { Console.WriteLine(" Orders:"); foreach (string order in p.Orders) { Console.WriteLine(" Order: {0}", order); } } else { Console.WriteLine(" No orders!"); } } Console.ReadLine(); } } } </code></pre> <p>I have 2 problems with my code:</p> <ol> <li><s> When I run my app I should get list of 10 persons and for every person 5 orders, but from time to time (ones for 3-5 runs) for first person I get only 4 orders. <strong>How I can prevent such behaviour?</strong></s><br> <strong>solved! thanks to @juharr</strong></li> <li>How can I report progress from my threads? What I would like to get is one Function from my Program class that will be called every time order is added from service - I need that to show some kind of progress for every report. <br>I was trying solution described here: <a href="https://stackoverflow.com/a/3874184/965722">https://stackoverflow.com/a/3874184/965722</a>, but I'm wondering if there is an easier way. Ideally I would like to add delegate to <code>Service</code> class and place all Thread code there.<br> <strong>How should I add event and delegate to <code>Service</code> class and how to subscribe to it in Main method?</strong> </li> </ol> <hr> <p><strong>I'm using .NET 3.5</strong></p> <p>I've added this code to be able to get progress reports:</p> <pre><code>using System; using System.Collections.Generic; using System.Threading; namespace Testy { internal class Program { public class ServiceEventArgs : EventArgs { public ServiceEventArgs(int sId, int progress) { SId = sId; Progress = progress; } public int SId { get; private set; } public int Progress { get; private set; } } internal class Person { private static readonly object ordersLock = new object(); public int Id { get; set; } public string Name { get; set; } public List&lt;string&gt; Orders { get; private set; } public Person() { Orders = new List&lt;string&gt;(); } public void AddOrder(string order) { lock (ordersLock) //access across threads { Orders.Add(order); } } } internal class Service { public event EventHandler&lt;ServiceEventArgs&gt; ReportProgress; public int Id { get; private set; } public string Name { get; private set; } private int counter; public Service(int id, string name) { Id = id; Name = name; } public void Search(List&lt;Person&gt; list) //I get error when I use IList instead of List { counter = 0; foreach (Person p in list) { counter++; Search(p); Thread.Sleep(3000); } } private void Search(Person p) { p.AddOrder(string.Format("Order from {0,2}", Thread.CurrentThread.ManagedThreadId)); EventHandler&lt;ServiceEventArgs&gt; handler = ReportProgress; if (handler != null) { var e = new ServiceEventArgs(Id, counter); handler(this, e); } } } private static void Main() { const int count = 5; var services = new List&lt;Service&gt;(); for (int i = 1; i &lt;= count; i++) { services.Add(new Service(i, "Service " + i)); } var persons = new List&lt;Person&gt;(); for (int i = 1; i &lt;= 10; i++) { persons.Add(new Person {Id = i, Name = string.Format("Test {0}", i)}); } Console.WriteLine("Number of services: {0}", services.Count); Console.WriteLine("Number of persons: {0}", persons.Count); Console.WriteLine("Press ENTER to start..."); Console.ReadLine(); ManualResetEvent resetEvent = new ManualResetEvent(false); int toProcess = services.Count; foreach (Service service in services) { new Thread(() =&gt; { service.ReportProgress += service_ReportProgress; service.Search(persons); if (Interlocked.Decrement(ref toProcess) == 0) resetEvent.Set(); } ).Start(); } // Wait for workers. resetEvent.WaitOne(); foreach (Person p in persons) { if (p.Orders.Count != count) Console.WriteLine("{0,2} Person name: {1}, orders: {2}", p.Id, p.Name, p.Orders.Count); } Console.WriteLine("END :)"); Console.ReadLine(); } private static void service_ReportProgress(object sender, ServiceEventArgs e) { Console.CursorLeft = 0; Console.CursorTop = e.SId; Console.WriteLine("Id: {0,2}, Name: {1,2} - Progress: {2,2}", e.SId, ((Service) sender).Name, e.Progress); } } } </code></pre> <p>I've added custom EventArgs, event for Service class. In this configuration I should have 5 services running, but only 3 of them report progress.<br> I imagined that if I have 5 services I should have 5 events (5 lines showing progress).<br> This is probably because of threads, but I have no ideas how to solve this.</p> <p>Sample output now looks like this:</p> <pre><code>Number of services: 5 Number of persons: 10 Press ENTER to start... Id: 3, Name: Service 3 - Progress: 10 Id: 4, Name: Service 4 - Progress: 10 Id: 5, Name: Service 5 - Progress: 19 END :) </code></pre> <p>It should look like this:</p> <pre><code>Number of services: 5 Number of persons: 10 Press ENTER to start... Id: 1, Name: Service 1 - Progress: 10 Id: 2, Name: Service 2 - Progress: 10 Id: 3, Name: Service 3 - Progress: 10 Id: 4, Name: Service 4 - Progress: 10 Id: 5, Name: Service 5 - Progress: 10 END :) </code></pre> <hr> <p><strong>Last edit</strong><br> I've moved all my thread creation to separate class <code>ServiceManager</code> now my code looks like so:</p> <pre><code>using System; using System.Collections.Generic; using System.Threading; namespace Testy { internal class Program { public class ServiceEventArgs : EventArgs { public ServiceEventArgs(int sId, int progress) { SId = sId; Progress = progress; } public int SId { get; private set; } // service id public int Progress { get; private set; } } internal class Person { private static readonly object ordersLock = new object(); public int Id { get; set; } public string Name { get; set; } public List&lt;string&gt; Orders { get; private set; } public Person() { Orders = new List&lt;string&gt;(); } public void AddOrder(string order) { lock (ordersLock) //access across threads { Orders.Add(order); } } } internal class Service { public event EventHandler&lt;ServiceEventArgs&gt; ReportProgress; public int Id { get; private set; } public string Name { get; private set; } public Service(int id, string name) { Id = id; Name = name; } public void Search(List&lt;Person&gt; list) { int counter = 0; foreach (Person p in list) { counter++; Search(p); var e = new ServiceEventArgs(Id, counter); OnReportProgress(e); } } private void Search(Person p) { p.AddOrder(string.Format("Order from {0,2}", Thread.CurrentThread.ManagedThreadId)); Thread.Sleep(50*Id); } protected virtual void OnReportProgress(ServiceEventArgs e) { var handler = ReportProgress; if (handler != null) { handler(this, e); } } } internal static class ServiceManager { private static IList&lt;Service&gt; _services; public static IList&lt;Service&gt; Services { get { if (null == _services) Reload(); return _services; } } public static void RunAll(List&lt;Person&gt; persons) { ManualResetEvent resetEvent = new ManualResetEvent(false); int toProcess = _services.Count; foreach (Service service in _services) { var local = service; local.ReportProgress += ServiceReportProgress; new Thread(() =&gt; { local.Search(persons); if (Interlocked.Decrement(ref toProcess) == 0) resetEvent.Set(); } ).Start(); } // Wait for workers. resetEvent.WaitOne(); } private static readonly object consoleLock = new object(); private static void ServiceReportProgress(object sender, ServiceEventArgs e) { lock (consoleLock) { Console.CursorTop = 1 + (e.SId - 1)*2; int progress = (100*e.Progress)/100; RenderConsoleProgress(progress, '■', ConsoleColor.Cyan, String.Format("{0} - {1,3}%", ((Service) sender).Name, progress)); } } private static void ConsoleMessage(string message) { Console.CursorLeft = 0; int maxCharacterWidth = Console.WindowWidth - 1; if (message.Length &gt; maxCharacterWidth) { message = message.Substring(0, maxCharacterWidth - 3) + "..."; } message = message + new string(' ', maxCharacterWidth - message.Length); Console.Write(message); } private static void RenderConsoleProgress(int percentage, char progressBarCharacter, ConsoleColor color, string message) { ConsoleColor originalColor = Console.ForegroundColor; Console.ForegroundColor = color; Console.CursorLeft = 0; int width = Console.WindowWidth - 1; var newWidth = (int) ((width*percentage)/100d); string progBar = new string(progressBarCharacter, newWidth) + new string(' ', width - newWidth); Console.Write(progBar); if (!String.IsNullOrEmpty(message)) { Console.CursorTop++; ConsoleMessage(message); Console.CursorTop--; } Console.ForegroundColor = originalColor; } private static void Reload() { if (null == _services) _services = new List&lt;Service&gt;(); else _services.Clear(); for (int i = 1; i &lt;= 5; i++) { _services.Add(new Service(i, "Service " + i)); } } } private static void Main() { var services = ServiceManager.Services; int count = services.Count; var persons = new List&lt;Person&gt;(); for (int i = 1; i &lt;= 100; i++) { persons.Add(new Person {Id = i, Name = string.Format("Test {0}", i)}); } Console.WriteLine("Services: {0}, Persons: {1}", services.Count, persons.Count); Console.WriteLine("Press ENTER to start..."); Console.ReadLine(); Console.Clear(); Console.CursorVisible = false; ServiceManager.RunAll(persons); foreach (Person p in persons) { if (p.Orders.Count != count) Console.WriteLine("{0,2} Person name: {1}, orders: {2}", p.Id, p.Name, p.Orders.Count); } Console.CursorTop = 12; Console.CursorLeft = 0; Console.WriteLine("END :)"); Console.CursorVisible = true; Console.ReadLine(); } } } </code></pre>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload