I need a certain bit of code to wait for an item to be added to a collection. The thread itself needs to wait until this completes before it continues. The item gets added to the collection from another thread (kicked off from elsewhere), and I have no way to signal the current thread that this has occurred.
A naive implementation might look something like (ignoring thread safety on the collection, etc. for now):
public static List<string> myList = new List<string>();
public void WaitingMethod(string id)
{
bool waiting = true;
while (waiting)
{
int index = myList.IndexOf(id);
if (index >= 0)
{
waiting = false;
}
}
//thread continues here
}
I believe this would block the thread and peg the processor.
What would be a more efficient way to achieve this?
Could you instead use an observable collection, and subscribe to CollectionChanged event?
namespace ConsoleApplication3
{
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
internal class Program
{
private static readonly ObservableCollection<string> MyList = new ObservableCollection<string>();
private static AutoResetEvent resetEvent = new AutoResetEvent(false);
private static void Main(string[] args)
{
Task.Factory.StartNew(
() =>
{
for (int i = 0; i < 10; i++)
{
string item = i.ToString("0000");
MyList.Add(item);
Console.WriteLine(item);
Thread.Sleep(1000);
}
});
MyList.CollectionChanged += (sender, eventArgs) =>
{ if (eventArgs.NewItems.Cast<string>().Any(a => a.Equals("0005"))) resetEvent.Set(); };
resetEvent.WaitOne();
}
}
}
Here is how you can also control the loop generating the items being added to the collection:
namespace ConsoleApplication3
{
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
internal class Program
{
#region Static Fields
private static readonly CancellationTokenSource Cts = new CancellationTokenSource();
private static readonly ObservableCollection<string> MyList = new ObservableCollection<string>();
private static readonly AutoResetEvent ResetEvent = new AutoResetEvent(false);
#endregion
#region Methods
private static void Main(string[] args)
{
Task task = Task.Factory.StartNew(
() =>
{
for (int i = 0; i < 10 && !Cts.IsCancellationRequested; i++)
{
string item = i.ToString("0000");
MyList.Add(item);
Console.WriteLine(item);
Thread.Sleep(100);
}
},
Cts.Token);
Task finish = task.ContinueWith(antecedent => { Console.WriteLine("Task finished. Status {0}", antecedent.Status); });
MyList.CollectionChanged += (sender, eventArgs) =>
{
if (eventArgs.NewItems.Cast<string>().Any(a => a.Equals("0005")))
{
Cts.Cancel();
ResetEvent.Set();
}
};
ResetEvent.WaitOne();
Task.WaitAll(finish);
}
#endregion
}
}
As the others have said, use ObservableCollection. But, you also need an AutoResetEvent:
In the class, during instantiation or program initialization, you register a Collection Changed event handler with the ObservableCollection. When this method is called, it calls the AutoResetEvent's Set method to signal that the collection was changed.
When your thread gets to the point that it needs to wait, you wait for the AutoResetEvent.
Your other thread does its thing and changes the collection. That calls the method you registered with the collection, which raises the AutoResetEvent. That signals your thread that's waiting to wake up.
This way, the thread that's waiting has no impact on the CPU.
Related
I'm trying to create a file copy application. I have a BackgroudWorker doing the work and it works fine so far. How it works, I have a form, Source and Destination Folder fields and a Copy button. The Copy button triggers the Copy operation using the BackgroundWorker. The Progressbar gets updated etc. Now I need to implemet a queue type of operation. I need to add other Source and Destination Copy operation and add it to the queue. I tried using the following:
Queue<MethodInvoker> MyQueue = new Queue<MethodInvoker>();
MyQueue.Enqueue(new MethodInvoker(() =>CopyStuff(1)));
MyQueue.Enqueue(new MethodInvoker(() =>CopyStuff(2)));
MethodInvoker bb = MyQueue.Dequeue(); //(I Forgot this part)
bb();
bb = MyQueue.Dequeue();
bb();
The problem is, because it's a BackgroundWorker, it does not wait for the first operation to complete. Any suggestions on how to go about this?
After fixing my code it works, except for it running on the UI thread, locking controls.
Update 1:
This works, but runs on main thread, user can't use controls while it's running:
BlockingCollection<MethodInvoker> bCollection = new BlockingCollection<MethodInvoker>(boundedCapacity: 2);
Task producerThread = Task.Factory.StartNew(() =>
{
for (int i = 0; i < 2; ++i)
{
bCollection.Add(CopyStuff);
}
bCollection.CompleteAdding();
});
foreach (MethodInvoker item in bCollection.GetConsumingEnumerable())
{
BeginInvoke(item);
}
Update 2:
Works in a Console App, but not a Windows Forms Application.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace AsyncCopy
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
}
public class AsyncCopy
{
private static Queue<Action> MyQueue = new Queue<Action>();
public async Task EnqueueAndCopy(Action[] actionList)
{
foreach (var action in actionList)
{
MyQueue.Enqueue(action);
}
while (MyQueue.TryDequeue(out var copyAction)) //Here's the problem on Windows Form Applivcation
{
//If the copyaction is itself async, await directly on the method here instead of running the action in a Task
await Task.Factory.StartNew(copyAction);
}
}
}
private void button1_Click(object sender, EventArgs e)
{
var asyncCopy = new AsyncCopy();
//In a typical usage, this will call be awaited
//In your case, make this call from a async method and call it from the UI thread
asyncCopy.EnqueueAndCopy(
new Action[] {
() => CopyStuff (1),
() => CopyStuff (2)
}
);
//Loop to confirm the processing is asynchronous
for (int i = 0; i <= 20; i++)
{
Console.WriteLine($"{i}");
Thread.Sleep(300);
}
}
//Copy process simulation
static void CopyStuff(int i)
{
Console.WriteLine($"Copying {i}");
Thread.Sleep(3000);
Console.WriteLine($"Copied {i}");
}
}
}
If you need to Queue the Copy process asynchronously as the other copies are processing, I would recommend a producer consumer pattern. Refer https://www.dotnetcurry.com/patterns-practices/1407/producer-consumer-pattern-dotnet-csharp
But a simple async await would work in your case as well
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Threading;
namespace stackoverflow {
class Program {
static void Main (string[] args) {
var asyncCopy = new AsyncCopy ();
//In a typical usage, this will call be awaited
//In your case, make this call from a async method and call it from the UI thread
asyncCopy.EnqueueAndCopy (
new Action[] {
() => CopyStuff (1),
() => CopyStuff (2)
}
);
//Loop to confirm the processing is asynchronous
for(int i=0; i <= 20; i++)
{
Console.WriteLine($"{i}");
Thread.Sleep(300);
}
}
//Copy process simulation
static void CopyStuff (int i) {
Console.WriteLine ($"Copying {i}");
Thread.Sleep(3000);
Console.WriteLine ($"Copied {i}");
}
}
public class AsyncCopy {
private static Queue<Action> MyQueue = new Queue<Action> ();
public async Task EnqueueAndCopy (Action[] actionList) {
foreach (var action in actionList) {
MyQueue.Enqueue (action);
}
while (MyQueue.TryDequeue (out var copyAction)) {
//If the copyaction is itself async, await directly on the method here instead of running the action in a Task
await Task.Factory.StartNew (copyAction);
}
}
}
}
Update
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private async Task CopyAsync(IEnumerable<Action> actionList)
{
foreach (var action in actionList)
{
await Task.Factory.StartNew(action);
}
}
private async void button2_Click(object sender, EventArgs e)
{
await CopyAsync(
new Action[]
{
() => CopyStuff(1),
() => CopyStuff(2)
});
}
//Copy process simulation
static void CopyStuff(int i)
{
Thread.Sleep(3000);
MessageBox.Show(string.Format("File Copied {0}", i));
}
}
}
I want to implement a capped and buffered job executor.
It will have a single method:
public class CappedBufferedExecutor {
public CappedBufferedExecutor(int bufferCapping, int fillTimeInMillisec);
public Task<bool> EnqueueAsync(string val);
}
The idea is that values are asynchronously enqueued, and once fillTimeInMillisec milliseconds pass, or the buffer is filled to its cap of unique values, the execution is made in practice and the async tasks all complete. While the execution is done (which could take a long time), the buffer can be re-filled and new async executions can be made.
I thought of something in the lines of the following pseudo code
Using a Timer, wait for the fillTime to pass, once elapsed, create a new task, that will do the work (see below).
On new value, lock a rwlock for read. Check if buffer is full, if so wait on a ManualResetEvent or a TaskCompletionSource.
Add new value to buffer (HashSet<string>).
If buffer is full, create a new execution task, that will lock the rwlock for write, do the work on all collected values and wake up all pending tasks using a TaskCompletionSource.
Wait on the TaskCompletionSource for the buffered task (mentioned in previous step) to be executed.
My problems: how to synchronize the Timer and the filled buffer check, how to wait when buffer is full, how to switch between TaskCompletionSource instances when starting to execute and allowing new values to arrive.
This is just concept, so don't expect much :-)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp
{
class Program
{
static void Main (string[] args)
{
var buffer = CreateBuffer ();
var executor = new Executor<string> (SomeWork, buffer);
executor.ProcessingStarted += Executor_ProcessingStarted;
string userInput = null;
do
{
userInput = Console.ReadLine ();
buffer.Enqueue (userInput);
}
while (!string.IsNullOrWhiteSpace (userInput));
executor.Dispose ();
}
//----------------------------------------------------------------------------------------------------------------------------------
private static IBuffer<string> CreateBuffer ()
{
var buffer = new UniqueItemsBuffer<string> (3);
buffer.DataAvailable += (items) => Console.WriteLine ("BUFFER :: data available raised.");
var alert = new Alert ();
var bufferWithTimeout = new BufferWithTimeout<string> (buffer, alert, TimeSpan.FromSeconds (5));
return bufferWithTimeout;
}
//----------------------------------------------------------------------------------------------------------------------------------
static Random rnd = new Random (); // must be outside, to avoid creating Random too quick because it will use the same seed for all tasks
public static bool SomeWork (string x)
{
int delay = rnd.Next (1000, 8000);
Console.WriteLine ($" +++ Starting SomeWork for: {x}, delay: {delay} ms");
Thread.Sleep (delay);
Console.WriteLine ($" --- SomeWork for: {x} - finished.");
return true;
}
//----------------------------------------------------------------------------------------------------------------------------------
private static void Executor_ProcessingStarted (IReadOnlyList<Task<bool>> items)
{
Task.Run (() =>
{
Task.WaitAll (items.ToArray ());
Console.WriteLine ("Finished processing tasks, count = " + items.Count);
});
}
}
//====== actual code ===================================================================================================================
public delegate void ItemsAvailable<T> (IReadOnlyList<T> items); // new type to simplify code
public delegate bool ProcessItem<T> (T item); // processes the given item and returns true if job is done with success
//======================================================================================================================================
public interface IDataAvailableEvent<T>
{
event ItemsAvailable<T> DataAvailable; // occurs when buffer need to be processed (also before raising this event, buffer should be cleared)
}
//======================================================================================================================================
public interface IProcessingStartedEvent<T>
{
event ItemsAvailable<Task<bool>> ProcessingStarted; // executor raises this event when all tasks are created and started
}
//======================================================================================================================================
public interface IBuffer<T> : IDataAvailableEvent<T>
{
bool Enqueue (T item); // adds new item to buffer (but sometimes it can ignore item, for example if we need only unique items in list)
// returns: true = buffer is not empty, false = is emtpy
void FlushBuffer (); // should clear buffer and raise event (or not raise if buffer was already empty)
}
//======================================================================================================================================
// raises DataAvailable event when buffer cap is reached
// ignores duplicates
// you can only use this class from one thread
public class UniqueItemsBuffer<T> : IBuffer<T>
{
public event ItemsAvailable<T> DataAvailable;
readonly int capacity;
HashSet<T> items = new HashSet<T> ();
public UniqueItemsBuffer (int capacity = 10)
{
this.capacity = capacity;
}
public bool Enqueue (T item)
{
if (items.Add (item) && items.Count == capacity)
{
FlushBuffer ();
}
return items.Count > 0;
}
public void FlushBuffer ()
{
Console.WriteLine ("BUFFER :: flush, item count = " + items.Count);
if (items.Count > 0)
{
var itemsCopy = items.ToList ();
items.Clear ();
DataAvailable?.Invoke (itemsCopy);
}
}
}
//======================================================================================================================================
public class Executor<T> : IProcessingStartedEvent<T>, IDisposable
{
public event ItemsAvailable<Task<bool>> ProcessingStarted;
readonly ProcessItem<T> work;
readonly IDataAvailableEvent<T> dataEvent;
public Executor (ProcessItem<T> work, IDataAvailableEvent<T> dataEvent)
{
this.work = work;
this.dataEvent = dataEvent;
dataEvent.DataAvailable += DataEvent_DataAvailable;
}
private void DataEvent_DataAvailable (IReadOnlyList<T> items)
{
Console.WriteLine ("EXECUTOR :: new items to process available, count = " + items.Count);
var list = new List<Task<bool>> ();
foreach (var item in items)
{
var task = Task.Run (() => work (item));
list.Add (task);
}
Console.WriteLine ("EXECUTOR :: raising processing started event (this msg can appear later than messages from SomeWork)");
ProcessingStarted?.Invoke (list);
}
public void Dispose ()
{
dataEvent.DataAvailable -= DataEvent_DataAvailable;
}
}
//======================================================================================================================================
// if you want to fill buffer using many threads - use this decorator
public sealed class ThreadSafeBuffer<T> : IBuffer<T>
{
public event ItemsAvailable<T> DataAvailable;
readonly IBuffer<T> target;
readonly object sync = new object ();
private ThreadSafeBuffer (IBuffer<T> target)
{
this.target = target;
this.target.DataAvailable += (items) => DataAvailable?.Invoke (items); // TODO: unpin event :P
}
public bool Enqueue (T item)
{
lock (sync) return target.Enqueue (item);
}
public void FlushBuffer ()
{
lock (sync) target.FlushBuffer ();
}
public static IBuffer<T> MakeThreadSafe (IBuffer<T> target)
{
if (target is ThreadSafeBuffer<T>) return target;
return new ThreadSafeBuffer<T> (target);
}
}
//======================================================================================================================================
// and now if you want to process buffer after elapsed time
public interface IAlert
{
CancellationTokenSource CreateAlert (TimeSpan delay, Action action); // will execute 'action' after given delay (non blocking)
}
// I didn't use much timers, so idk is this code good
public class Alert : IAlert
{
List<System.Timers.Timer> timers = new List<System.Timers.Timer> (); // we need to keep reference to timer to avoid dispose
public CancellationTokenSource CreateAlert (TimeSpan delay, Action action)
{
var cts = new CancellationTokenSource ();
var timer = new System.Timers.Timer (delay.TotalMilliseconds);
timers.Add (timer);
timer.Elapsed += (sender, e) =>
{
timers.Remove (timer);
timer.Dispose ();
if (cts.Token.IsCancellationRequested) return;
action.Invoke ();
};
timer.AutoReset = false; // just one tick
timer.Enabled = true;
return cts;
}
}
// thread safe (maybe :-D)
public class BufferWithTimeout<T> : IBuffer<T>
{
public event ItemsAvailable<T> DataAvailable;
readonly IBuffer<T> target;
readonly IAlert alert;
readonly TimeSpan timeout;
CancellationTokenSource cts;
readonly object sync = new object ();
public BufferWithTimeout (IBuffer<T> target, IAlert alert, TimeSpan timeout)
{
this.target = ThreadSafeBuffer<T>.MakeThreadSafe (target); // alert can be raised from different thread
this.alert = alert;
this.timeout = timeout;
target.DataAvailable += Target_DataAvailable; // TODO: unpin event
}
private void Target_DataAvailable (IReadOnlyList<T> items)
{
lock (sync)
{
DisableTimer ();
}
DataAvailable?.Invoke (items);
}
public bool Enqueue (T item)
{
lock (sync)
{
bool hasItems = target.Enqueue (item); // can raise underlying flush -> dataAvailable event (will disable timer)
// and now if buffer is empty, we cannot start timer
if (hasItems && cts == null) // if timer is not enabled
{
Console.WriteLine ("TIMER :: created alert");
cts = alert.CreateAlert (timeout, HandleAlert);
}
return hasItems;
}
}
public void FlushBuffer ()
{
lock (sync)
{
DisableTimer ();
target.FlushBuffer ();
}
}
private void HandleAlert ()
{
lock (sync)
{
Console.WriteLine ("TIMER :: handler, will call buffer flush");
target.FlushBuffer ();
}
}
private void DisableTimer ()
{
cts?.Cancel ();
cts = null;
Console.WriteLine ("TIMER :: disable");
}
}
}
You can do something easily using Reactive Extensions. A basic example using the Buffer method:
void Main()
{
var c = new Processor();
c.SetupBufferedProcessor(2, TimeSpan.FromMilliseconds(1000));
c.Enqueue("A");
c.Enqueue("B");
c.Enqueue("C");
Console.ReadLine();
// When application has ended, flush the buffer
c.Dispose();
}
public sealed class Processor : IDisposable
{
private IDisposable subscription;
private Subject<string> subject = new Subject<string>();
public void Enqueue(string item)
{
subject.OnNext(item);
}
public void SetupBufferedProcessor(int bufferSize, TimeSpan bufferCloseTimespan)
{
// Create a subscription that will produce a set of strings every second
// or when buffer has 2 items, whatever comes first
subscription = subject.AsObservable()
.Buffer(bufferCloseTimespan, bufferSize)
.Where(list => list.Any()) // suppress empty list (no items enqueued for 1 second)
.Subscribe(async list =>
{
await Task.Run(() =>
{
Console.WriteLine(string.Join(",", list));
Thread.Sleep(2000); // For demo purposes, to demonstrate processing takes place parallel with other batches.
});
});
}
public void Dispose()
{
subscription?.Dispose();
}
}
This will output
A,B
and, after one second,
C
The code for rx is at GitHub
More on rx: http://www.introtorx.com/
This example can be improved to hold references to the created Task objects so they can be properly awaited before ending the application but this will give you the general idea.
Below code is not working usin Auto Reset event, what wrong i am doing ?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace Threaddd
{
class Program
{
static int num = 0;
static EventWaitHandle e = new AutoResetEvent(false);
static object o = new object();
static void Main(string[] args)
{
new Thread(Consumer).Start();
new Thread(Producer).Start();
}
static void Producer()
{
while (true)
{
if (num == 0)
{
num++;
Console.WriteLine("Produced " + num);
Thread.Sleep(1000);
e.Set();
e.WaitOne();
}
}
}
static void Consumer()
{
while (true)
{
if (num == 1)
{
Console.WriteLine("Consumed " + num);
Thread.Sleep(1000);
num--;
e.Set();
e.WaitOne();
}
else
{
e.WaitOne();
}
}
}
}
It looks like when the Producer thread calls e.Set(), it does not notify the Consumer thread immediately, so the Producer thread consumes the event when it calls e.WaitOne().
From http://msdn.microsoft.com/en-us/library/system.threading.autoresetevent.aspx
"There is no guarantee that every call to the Set method will release a thread. If two calls are too close together, so that the second call occurs before a thread has been released, only one thread is released. It is as if the second call did not happen. Also, if Set is called when there are no threads waiting and the AutoResetEvent is already signaled, the call has no effect."
One idea would be to use a separate event for each thread as the supplied link illustrates.
This is not a real consumer / producer pattern implementation.
e.Set() will release only ONE thread that is waiting using e.WaitOne()
So, when you write:
e.Set();
e.WaitOne();
On the producer thread, you are actually not enabling the consumer thread to get the signal
Try the following:
static void Producer()
{
while (true)
{
Thread.Sleep(1000);
Console.WriteLine("Produced " + num++);
e.Set();
}
}
static void Consumer()
{
while (true)
{
e.WaitOne();
Console.WriteLine("Consumed " + num);
}
}
If your okay with your consumer and producer thread running wild you can simplify your program by removing some of the sets and waitones:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace Threaddd
{
internal class Program
{
private static int num = 0;
private static EventWaitHandle e = new AutoResetEvent(false);
private static object o = new object();
private static void Main(string[] args)
{
new Thread(Consumer).Start();
new Thread(Producer).Start();
}
private static void Producer()
{
while (true)
{
if (num == 0)
{
num++;
Console.WriteLine("Produced " + num);
Thread.Sleep(1000);
e.Set();
}
}
}
private static void Consumer()
{
while (true)
{
if (num == 1)
{
Console.WriteLine("Consumed " + num);
Thread.Sleep(1000);
num--;
e.WaitOne();
}
}
}
}
}
If that is not an option both your producer and consumer(s) must have their own events.
To keep num between 0 and 1 you can use the following pattern and lose the if statements:
class Program
{
static volatile int num = 0;
// Initialized set to ensure that the producer goes first.
static EventWaitHandle consumed = new AutoResetEvent(true);
// Initialized not set to ensure consumer waits until first producer run.
static EventWaitHandle produced = new AutoResetEvent(false);
static void Main(string[] args)
{
new Thread(Consumer).Start();
new Thread(Producer).Start();
}
static void Producer()
{
while (true)
{
consumed.WaitOne();
num++;
Console.WriteLine("Produced " + num);
Thread.Sleep(1000);
produced.Set();
}
}
static void Consumer()
{
while (true)
{
produced.WaitOne();
Console.WriteLine("Consumed " + num);
Thread.Sleep(1000);
num--;
consumed.Set();
}
}
}
It is worth pointing out that normally there is some kind of queue between the producer and the consumer so that the producer can create more than one item between each run of the consumer. The way I have written the above there is little point having the consumer and producer on separate threads as they will not be able to run at the same time.
I have an example here that replicates what I am trying to accomplish.
As the following code will show - I have ViewModel that updates a ObservableCollection Property bound to a View. Normally I would update the collection from a result retrieved from a Model, but hopefully this example will suffice.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Controls;
namespace MVVMWpf.ViewModel
{
public class ListViewModel
{
public ObservableCollection<int> SomeObjectCollection { get; set; }
public ListViewModel()
{
SomeObjectCollection = new ObservableCollection<int>();
}
public void Do()
{
for (int i = 1; i < 1000000; i++)
{
int i1 = i;
SomeObjectCollection.Add(i1);
}
}
}
}
Unfortunately, this blocks this UI. It will only update the View when loop runs to completion. The way I solved it breaks MVVM concepts. That's why I need your help. I did it this way.
public class ListViewModel
{
private delegate void LongRunningProcess();
public ObservableCollection<int> SomeObjectCollection { get; set; }
private ListBox listBox;
public ListViewModel(ListBox listBox)
{
this.listBox = listBox;
SomeObjectCollection = new ObservableCollection<int>();
}
public void Do()
{
Thread thread = new Thread(() =>
{
for (int i = 1; i < int.MaxValue; i++)
{
int i1 = i;
listBox.Dispatcher.Invoke(
new LongRunningProcess(() =>
SomeObjectCollection.Add(i1);
}});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}
}
As you can see the ViewModel now knows about the listBox element from the UI. And looking at the MVVM diagram only the View should have a reference to the ViewModel through binding. How does one overcome this issue?
Thanks.
You need to let your loop unleash updates to the screen - some kind of DoEvents() will do:
public static void DoEvents()
{
Application.Current.Dispatcher.Invoke(
DispatcherPriority.Background,new Action(delegate { }));
}
Add it and call that from within your loop.
Using timer as another option, your code should look somthing like that:
private System.Timers.Timer operationsTimer = new System.Timers.Timer();
private int x;
in your ctor:
operationsTimer.Elapsed += new System.Timers.ElapsedEventHandler
(operationsTimer_Elapsed);
operationsTimer.Enabled = true;
in your timer elpased:
operationsTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
operationsTimer.Enabled = false;
//add item to collection code
x++;
if(x<100)
operationsTimer.Enabled = true;
}
Consider using a BackgroundWorker, an easy way of performing async tasks with functionality for reporting progress as well as a completed event. Best of all, you don't have to invoke anything on the dispatcher, as the BackgroundWorker's functions are synchronized to the UI thread.
Scenario
I have a background worker in my application that runs off and does a bunch of processing. I specifically used this implementation so as to keep my User Interface fluid and prevent it from freezing up. I want to keep the background worker, but inside that thread, spawn off ONLY 3 MORE threads - making them share the processing (currently the worker thread just loops through and processes each asset one-by-one. However I would like to speed this up but using only a limited number of threads.
Question
Given the code below, how can I get the loop to choose a thread that is free, and then essentially wait if there isn't one free before it continues.
CODE
foreach (KeyValuePair<int, LiveAsset> kvp in laToHaganise)
{
Haganise h = new Haganise(kvp.Value,
busDate,
inputMktSet,
outputMktSet,
prodType,
noOfAssets,
bulkSaving);
h.DoWork();
}
Thoughts
I'm guessing that I would have to start off by creating 3 new threads, but my concern is that if I'm instantiating a new Haganise object each time - how can I pass the correct "h" object to the correct thread.....
Thread firstThread = new Thread(new ThreadStart(h.DoWork));
Thread secondThread =new Thread(new ThreadStart(h.DoWork));
Thread thirdThread = new Thread(new ThreadStart(h.DoWork));
Help greatly appreciated.
Depending on the size of the job the thread has to do, ThreadPool.QueueUserWorkItem may be a good option. That will create a thread and put it in a queue and execute it when there is thread in the pool available.
Alternatively you could create your own type of Pool which has 3 threads and use ManualResetEvent to determine when a particular thread is finished.
Two possibilities:
Use the ThreadPool.QueueUserWorkItem (as already mentioned by James)
Implement it BackgroundWorkers on yourself (see below)
Here is a working example, that selects the next free worker:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading;
namespace ConsoleApplication1
{
class Program
{
private static List<MyWorker> _Workers;
static void Main(string[] args)
{
_Workers = new List<MyWorker>();
for (int i = 0; i < 5; i++)
{
_Workers.Add(CreateDefaultWorker(i));
}
StartJobs(20000);
Console.ReadKey();
}
private static void StartJobs(int runtime)
{
Random rand = new Random();
DateTime startTime = DateTime.Now;
while (DateTime.Now - startTime < TimeSpan.FromMilliseconds(runtime))
{
var freeWorker = GetFreeWorker();
if (freeWorker != null)
{
freeWorker.Worker.RunWorkerAsync(new Action(() => DoSomething(freeWorker.Index, rand.Next(500, 2000))));
}
else
{
Console.WriteLine("No free worker available!");
Console.WriteLine("Waiting for free one...");
WaitForFreeOne();
}
}
}
private static MyWorker GetFreeWorker()
{
foreach (var worker in _Workers)
{
if (!worker.Worker.IsBusy)
return worker;
}
return null;
}
private static void WaitForFreeOne()
{
while (true)
{
foreach (var worker in _Workers)
{
if (!worker.Worker.IsBusy)
return;
}
Thread.Sleep(1);
}
}
private static MyWorker CreateDefaultWorker(int index)
{
var worker = new MyWorker(index);
worker.Worker.DoWork += (sender, e) => ((Action)e.Argument).Invoke();
worker.Worker.RunWorkerCompleted += (sender, e) => Console.WriteLine("Job finished in worker " + worker.Index);
return worker;
}
static void DoSomething(int index, int timeout)
{
Console.WriteLine("Worker {1} starts to work for {0} ms", timeout, index);
Thread.Sleep(timeout);
}
}
public class MyWorker
{
public int Index { get; private set; }
public BackgroundWorker Worker { get; private set; }
public MyWorker(int index)
{
Index = index;
Worker = new BackgroundWorker();
}
}
}