Best way to update cmdlet progress from a separate thread - c#

I have a powershell cmdlet written in C# (deriving from PSCmdlet) which will start a long-running task which should update its progress using WriteProgress() while it is running. Since powershell will not allow a separate thread to use WriteObject or WriteProgress I had to create a Queue<object> in the main thread and I add items to the queue from the task that I want to be written to the Pipeline/Progress. A while loop will dequeue objects as they come in and write to the pipline / progress bar.
This is working, but I wanted to see if there were any better practices for multi-threading with a powershell cmdlet that is written in C#/VB. For example with WPF I can always step onto the UI thread with UIComponent.Dispatcher.Invoke() if I need to update a progress bar or UI Component. Is there anything equivalent that I can use to 'step onto' the powershell thread to update the UI or write to the pipeline?

Here is an example of the queue system encapsulated in a class so it is easier to use and mimics Cmdllet.WriteObject's behavior. This way you can call WriteObject from within the separate thread and the object will be marshalled onto the powershell thread and written to the pipeline.
[Cmdlet("Test", "Adapter")]
public class TestCmdlet : PSCmdlet
{
protected override void ProcessRecord()
{
PowerShellAdapter adapter = new PowerShellAdapter(this, 100);
Task.Factory.StartNew(() => {
for (int x = 0; x < 100; x++) {
adapter.WriteObject(x);
Thread.Sleep(100);
}
adapter.Finished = true;
});
adapter.Listen();
}
}
public class PowerShellAdapter
{
private Cmdlet Cmdlet { get; set; }
private Queue<object> Queue { get; set; }
private object LockToken { get; set; }
public bool Finished { get; set; }
public int Total { get; set; }
public int Count { get; set; }
public PowerShellAdapter(Cmdlet cmdlet, int total)
{
this.Cmdlet = cmdlet;
this.LockToken = new object();
this.Queue = new Queue<object>();
this.Finished = false;
this.Total = total;
}
public void Listen()
{
ProgressRecord progress = new ProgressRecord(1, "Counting to 100", " ");
while (!Finished || Queue.Count > 0)
{
while (Queue.Count > 0)
{
progress.PercentComplete = ++Count*100 / Total;
progress.StatusDescription = Count + "/" + Total;
Cmdlet.WriteObject(Queue.Dequeue());
Cmdlet.WriteProgress(progress);
}
Thread.Sleep(100);
}
}
public void WriteObject(object obj)
{
lock (LockToken)
Queue.Enqueue(obj);
}
}

The answer provided by Despertar will work, but it can be improved on slightly.
Polling in a loop with Thread.Sleep should be replaced with use of an AutoResetEvent. This will cause the main thread to only "wake up" when there is actually data available, and can allow the cmdlet to complete faster than 100ms. The Thread.Sleep will always cause the cmdlet to take at least 100ms, even if it could run must faster. This might not be a problem if you have a simple cmdlet, but if you insert it into a complex pipeline this 100ms can easily multiply and cause things to run very slowly. Additionally, a lock should be taken when accessing the Queue on the main thread inside the Listen method.
The moral of the story: if you do cross-thread synchronization Thread.Sleep is not the right tool.
using System.Threading;
public class PowerShellAdapter
{
private Cmdlet Cmdlet { get; set; }
private Queue<object> Queue { get; set; }
AutoResetEvent sync;
private object LockToken { get; set; }
// volatile, since it will be written/read from different threads.
volatile bool finished;
public bool Finished
{
get { return finished; }
set
{
this.finished = value;
// allow the main thread to exit the outer loop.
sync.Set();
}
}
public int Total { get; set; }
public int Count { get; set; }
public PowerShellAdapter(Cmdlet cmdlet, int total)
{
this.Cmdlet = cmdlet;
this.LockToken = new object();
this.Queue = new Queue<object>();
this.finished = false;
this.Total = total;
this.sync = new AutoResetEvent(false);
}
public void Listen()
{
ProgressRecord progress = new ProgressRecord(1, "Counting to 100", " ");
while (!Finished)
{
while (true) { // loop until we drain the queue
object item;
lock (LockToken) {
if (Queue.Count == 0)
break; // exit while
item = Queue.Dequeue();
}
progress.PercentComplete = ++Count * 100 / Total;
progress.StatusDescription = Count + "/" + Total;
Cmdlet.WriteObject(item);
Cmdlet.WriteProgress(progress);
}
sync.WaitOne();// wait for more data to become available
}
}
public void WriteObject(object obj)
{
lock (LockToken)
{
Queue.Enqueue(obj);
}
sync.Set(); // alert that data is available
}
}
Note, I haven't actually tested this code, but it illustrates the idea.

You could take a look at the Start-Job cmdlet together with Get-Job, Wait-Job and Receive-Job.
Start-Job will effectively start a new thread and output a JobId which you can query with Receive-Job to get the output. You could then loop through all currently running jobs and update your progress bar.
Take a look at http://blogs.technet.com/b/heyscriptingguy/archive/2012/08/10/use-background-jobs-to-run-a-powershell-server-uptime-report.aspx

Related

How do I guarantee execution of code only if and when optional main thread task and worker threads are finished?

Background:
I have an application I am developing that deals with a large number of addons for another application. One if its primary uses is to safely modify file records in files with fewer records so that they may be treated as one file (almost as if it is combing the files together into one set of records. To do this safely it keeps track of vital information about those files and changes made to them so that those changes can be undone if they don't work as expected.
When my application starts, it analyzes those files and keeps essential properties in a cache (to reduce load times). If a file is missing from the cache, the most important stuff is retrieved and then a background worker must process the file for more information. If a file that was previously modified has been updated with a new version of the file, the UI must confirm this with the user and its modification data removed. All of this information, including information on its modification is stored in the cache.
My Problem:
My problem is that neither of these processes are guaranteed to run (the confirmation window or the background file processor). If either of them run, then the cache must be updated by the main thread. I don't know enough about worker threads, and which thread runs the BackgroundWorker.RunWorkerCompleted event handler in order to effectively decide how to approach guaranteeing that the cache updater is run after either (or both) processes are completed.
To sum up: if either process is run, they both must finish and (potentially) wait for the other to be completed before running the cache update code. How can I do this?
ADJUNCT INFO (My current intervention that doesn't seem to work very well):
I have a line in the RunWorkerCompleted handler that waits until the form reference is null before continuing and exiting but maybe this was a mistake as it sometimes locks my program up.
SpinWait.SpinUntil(() => overwriteForm == null);
I haven't included any more code because I anticipate that this is more of a conceptual question than a code one. However, if necessary, I can supply code if it helps.
I think CountDownTask is what you need
using System;
using System.Threading;
public class Program
{
public class AtomicInteger
{
protected int value = 0;
public AtomicInteger(int value)
{
this.value = value;
}
public int DecrementAndGet()
{
int answer = Interlocked.Decrement(ref value);
return answer;
}
}
public interface Runnable
{
void Run();
}
public class CountDownTask
{
private AtomicInteger count;
private Runnable task;
private Object lk = new Object();
private volatile bool runnable;
private bool cancelled;
public CountDownTask(Int32 count, Runnable task)
{
this.count = new AtomicInteger(count);
this.task = task;
this.runnable = false;
this.cancelled = false;
}
public void CountDown()
{
if (count.DecrementAndGet() == 0)
{
lock (lk)
{
runnable = true;
Monitor.Pulse(lk);
}
}
}
public void Await()
{
lock (lk)
{
while (!runnable)
{
Monitor.Wait(lk);
}
if (cancelled)
{
Console.WriteLine("Sorry! I was cancelled");
}
else {
task.Run();
}
}
}
public void Cancel()
{
lock (lk)
{
runnable = true;
cancelled = true;
Monitor.Pulse(lk);
}
}
}
public class HelloWorldTask : Runnable
{
public void Run()
{
Console.WriteLine("Hello World, I'm last one");
}
}
public static void Main()
{
Thread.CurrentThread.Name = "Main";
Console.WriteLine("Current Thread: " + Thread.CurrentThread.Name);
CountDownTask countDownTask = new CountDownTask(3, new HelloWorldTask());
Thread worker1 = new Thread(() => {
Console.WriteLine("Worker 1 run");
countDownTask.CountDown();
});
Thread worker2 = new Thread(() => {
Console.WriteLine("Worker 2 run");
countDownTask.CountDown();
});
Thread lastThread = new Thread(() => countDownTask.Await());
lastThread.Start();
worker1.Start();
worker2.Start();
//countDownTask.Cancel();
Console.WriteLine("Main Thread Run");
countDownTask.CountDown();
Thread.Sleep(1000);
}
}
let me explain (but you can refer Java CountDownLatch)
1. To ensure a task must run after another tasks, we need create a Wait function to wait for they done, so I used
while(!runnable) {
Monitor.Wait(lk);
}
2. When there is a task done, we need count down, and if count down to zero (it means all of the tasks was done) we will need notify to blocked thread to wake up and process task
if(count.decrementAndGet() == 0) {
lock(lk) {
runnable = true;
Monitor.Pulse(lk);
}
}
Let read more about volatile, thanks
While dung ta van's "CountDownTask" answer isn't quite what I needed, it heavily inspired the solution below (see it for more info). Basically all I did was add some extra functionality and most importantly: made it so that each task "vote" on the outcome (true or false). Thanks dung ta van!
To be fair, dung ta van's solution DOES work to guarantee execution which as it turns out isn't quite what I needed. My solution adds the ability to make that execution conditional.
This was my solution which worked:
public enum PendingBool
{
Unknown = -1,
False,
True
}
public interface IRunnableTask
{
void Run();
}
public class AtomicInteger
{
int integer;
public int Value { get { return integer; } }
public AtomicInteger(int value) { integer = value; }
public int Decrement() { return Interlocked.Decrement(ref integer); }
public static implicit operator int(AtomicInteger ai) { return ai.integer; }
}
public class TaskElectionEventArgs
{
public bool VoteResult { get; private set; }
public TaskElectionEventArgs(bool vote) { VoteResult = vote; }
}
public delegate void VoteEventHandler(object sender, TaskElectionEventArgs e);
public class SingleVoteTask
{
private AtomicInteger votesLeft;
private IRunnableTask task;
private volatile bool runTask = false;
private object _lock = new object();
public event VoteEventHandler VoteCast;
public event VoteEventHandler TaskCompleted;
public bool IsWaiting { get { return votesLeft.Value > 0; } }
public PendingBool Result
{
get
{
if (votesLeft > 0)
return PendingBool.Unknown;
else if (runTask)
return PendingBool.True;
else
return PendingBool.False;
}
}
public SingleVoteTask(int numberOfVotes, IRunnableTask taskToRun)
{
votesLeft = new AtomicInteger(numberOfVotes);
task = taskToRun;
}
public void CastVote(bool vote)
{
votesLeft.Decrement();
runTask |= vote;
VoteCast?.Invoke(this, new TaskElectionEventArgs(vote));
if (votesLeft == 0)
lock (_lock)
{
Monitor.Pulse(_lock);
}
}
public void Await()
{
lock(_lock)
{
while (votesLeft > 0)
Monitor.Wait(_lock);
if (runTask)
task.Run();
TaskCompleted?.Invoke(this, new TaskElectionEventArgs(runTask));
}
}
}
Implementing the above solution was as simple as creating the SingleVoteTask in the UI thread and then having each thread affecting the outcome cast a vote.

why this in a callback causes the code to stop?

After recovering my data with Firebase using a callback in GetValueAsync().ContinueWith(task..) , I would like to instantiate my prefab in order to see the list of scores for my leaderboard. But, it does nothing and I have no errors. The code simply stops in the callback UseSores as soon as it come across on a 'this' or a 'instantiate'.
public class Leaderboardmanager : MonoBehaviour
{
public GameObject rowLeardBoard;
FirebaseDB_Read read;
float positionX;
int nbRows = 10;
void Start()
{
read = (gameObject.AddComponent<FirebaseDB_Read>());
GetScorePlayer();
}
void GetScorePlayer()
{
read.GetScores(UseScores, "entries/LeaderBoard/", nbRows);
}
void UseScores(IList<FirebaseDB_Read.Score> scores)
{
Debug.Log("arrive here");
positionX = this.transform.position.y;
Debug.Log("does not arrive here");
}
}
Here is to get my data :
public class FirebaseDB_Read : MonoBehaviour
{
public class Score
{
public string UID;
public string score;
public int rank;
}
public void GetScores(Action<IList<Score>> callback, string URL_TO_SCORES, int limit)
{
DatabaseReference scoresRef = FirebaseDatabase.DefaultInstance.GetReference(URL_TO_SCORES);
scoresRef.OrderByChild("score").LimitToLast(limit).GetValueAsync().ContinueWith(task =>
{
DataSnapshot snapshot = task.Result;
IList<Score> objectsList = new List<Score> { };
int i = 1;
foreach (var childSnapshot in snapshot.Children)
{
Score score = new Score();
score.rank = i;
score.UID = childSnapshot.Child("UID").GetValue(true).ToString();
score.score = childSnapshot.Child("score").GetValue(true).ToString();
objectsList.Add(score);
i++;
}
callback(objectsList);
});
}
}
This is an often asked problem in Unity: Because you ContinueWith on a background thread!
Unity isn't thread-safe, meaning that most of the Unity API can only be used within the Unity main thread.
Firebase offers an extension specifically for Unity: ContinueWithOnMainThread which assures that the result is handled in the Unity main thread where accessing the API is valid.
scoresRef.OrderByChild("score").LimitToLast(limit).GetValueAsync().ContinueWithOnMainThread(task =>
{
...
});
As alternative you can use kind of a so called "main thread dispatcher" pattern and make sure that the callback is executed in the main thread on the receiver side. The advantage of this would be that the still expensive operations on your list are all executed on a background thread, not affecting the UI performance
scoresRef.OrderByChild("score").LimitToLast(limit).GetValueAsync().ContinueWith(task =>
{
...
});
but then on receiver side in FirebaseDB_Read
private readonly ConcurrentQueue<Action> _mainThreadActions = new ConcurrentQueue<Action>();
private void Update()
{
if(_mainThreadAction.Count > 0)
{
while(_mainThreadActions.TryDequeue(out var action))
{
action?.Invoke();
}
}
}
void GetScorePlayer()
{
read.GetScores(UseScores, "entries/LeaderBoard/", nbRows);
}
void UseScores(IList<FirebaseDB_Read.Score> scores)
{
// handle this in the next main thread update
_mainThreadActions.Enqueue(() =>
{
Debug.Log("arrive here");
positionX = this.transform.position.y;
Debug.Log("does not arrive here");
}
}
which on the offside of course introduces a little overhead for checking for any new actions in Update of course. So if you plan do use multiple of such background actions make sure to implement them in one central place in order to keep the overhead limited ;)

Creating a custom and reliable task scheduler

I'm trying to create my own C# task scheduler, so for example I want to run a specific void with an Id argument every Monday of the week. I also want to keep a list of all running tasks per scheduler.
So you would have a scheduler that contains a list of tasks and those tasks have actions and triggers, actions being the method(s) that I want to execute and triggers being for example every Monday of every week.
Now when the task is done and it has reached it's end date it has to pretty much dispose itself like it never existed. This is where I don't know what to do anymore.
Now this is an extreme example but I tried scheduling one million tasks that would run after 10 seconds. All the tasks ran but somehow were not disposed correctly. Visual Studio said that the Process Memory was about 700 MB and the Heap Memory about 2 MB after the tasks have disposed themselves.
I tried two things:
A flush system that runs every 30 seconds and buffers finished tasks and removes them from the list and then from the buffer. This worked kinda, after running one million tasks it would give me a "Collection was modified" exception.
Self disposing tasks, when the task is finished it will dispose of itself. When running this with one hundred thousand tasks it would dispose most of them and remove them from the list but I had at least five thousand tasks still in the task list.
My question is how do I correctly and reliably dispose the tasks and remove them from the task list so that they are no longer existing within the memory without getting any exceptions such as "Collection was modified".
Here is my code that I used, you might need to edit it a little to make it use the flush system and the self disposing system.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Timers;
using static TaskScheduler.Scheduler;
namespace TaskScheduler
{
internal class Program
{
public static void Main(string[] args)
{
Scheduler scheduler = new Scheduler(new TimeSpan(0, 0, 30));
for (int i = 0; i < 100000; i++)
{
scheduler.Schedule(Function, new Settings() { Id = i, Start = DateTime.Now.AddSeconds(10) });
}
scheduler.Schedule(Function, new Settings() { Id = 1123, Recurring = true, Start = DateTime.Now.AddSeconds(5), End = DateTime.Now.AddDays(14) });
while (true)
{
Console.WriteLine(scheduler.Tasks.Count());
System.Threading.Thread.Sleep(500);
}
}
public static void Function(Task task)
{
Console.WriteLine($"Test function: {task._settings.Id}");
}
}
public class Scheduler : IDisposable
{
public List<Task> Tasks = new List<Task>();
public List<Task> FlushCollection = new List<Task>();
private Timer timer; //Flush timer
public Scheduler(TimeSpan time)
{
timer = new Timer(time.TotalMilliseconds);
timer.Elapsed += new ElapsedEventHandler(Flush);
timer.Start();
}
public void Flush(object sender, ElapsedEventArgs args)
{
foreach (Task task in Tasks.ToArray())
{
if (task.timer == null)
{
FlushCollection.Add(task);
}
}
foreach(Task task in FlushCollection.ToArray())
{
Tasks.Remove(task);
}
FlushCollection.Clear();
}
public void Schedule(Action<Task> action, Settings settings)
{
Tasks.Add(new Task(this, action, settings));
}
public void Unschedule(Task task)
{
task.Dispose();
Tasks.Remove(task);
}
public void Unschedule(int id)
{
Unschedule(Tasks.Where(x => x._settings.Id == id).FirstOrDefault());
}
public void Dispose()
{
foreach (Task task in Tasks.ToArray())
{
task.Dispose();
}
Tasks.Clear();
}
public class Task : IDisposable
{
public Scheduler _scheduler;
public Action<Task> _action;
public Settings _settings;
public Timer timer;
private DateTime next;
public Task(Scheduler scheduler, Action<Task> action, Settings settings)
{
_scheduler = scheduler;
_action = action;
_settings = settings;
Init();
}
public void Init()
{
next = DateTime.Now + _settings.Interval;
timer = new Timer((_settings.Start - DateTime.Now).TotalMilliseconds);
timer.Elapsed += new ElapsedEventHandler(Elapsed);
timer.Start();
if (_settings.Interval.TotalMilliseconds != 0)
{
timer.Interval = _settings.Interval.TotalMilliseconds;
}
}
public void Elapsed(object sender, ElapsedEventArgs args)
{
if (!Ready())
{
return;
}
Run();
}
public void Dispose()
{
timer.Dispose();
timer = null;
}
public bool Ready()
{
return DateTime.Now >= next;
}
public void Run()
{
_action(this);
if (Expired() || !_settings.Recurring)
{
_scheduler.Unschedule(this);
}
}
public bool Expired()
{
if (DateTime.Now >= _settings.End)
{
return true;
}
return false;
}
}
public class Settings
{
public int? Id { get; set; }
public bool Recurring { get; set; } = false;
public TimeSpan Interval { get; set; } //Not required when not recurring.
public DateTime Start { get; set; } = DateTime.Now;
public DateTime End { get; set; } = DateTime.Now.AddTicks(1);
}
}
}
Keep in mind this is just a prototype so it doesn't contain the whole trigger and action system yet and other things I mentioned.
I will use Quartz.NET and or Hangfire as scheduler solution.
https://www.quartz-scheduler.net
https://www.hangfire.io

Blocking collections + Multiple Worker threads per blocking collection + Wait For Work Completion

I have to do action in batch of 1000 message say Action A, B, C. I can do these actions in parallel.
I created groups for them. To increase parallelism, I created subgroups with in each group. Task with in a subgroup needs to be executed serially. But two subgroups can execute in parallel.
After a batch of 1000 finishes, I have to do some processing ie save in db. But I am unable to understand , how to wait for all the task to finish (I am not interested in waiting in middle just at the end of 1000 taks). Any suggestions are welcome.
public class OrderlyThreadPool<t> : IDisposable
{
BlockingCollection<t> _workingqueue = null;
Action<t> _handler = null;
public OrderlyThreadPool(int wrkerCount, Action<t> handler)
{
_workingqueue = new BlockingCollection<t>();
_handler = handler;
Worker worker = new Worker(wrkerCount, Process); //WorkerCount is always 1
worker.Start();
}
public void AddItem(t item)
{
_workingqueue.Add(item);
}
private void Process()
{
foreach (t item in _workingqueue.GetConsumingEnumerable())
{
_handler(item);
}
}
public void Dispose()
{
_workingqueue.CompleteAdding();
_workingqueue = null;
}
}
public class Worker
{
int _wrkerCount = 0;
Action _action = null;
public Worker(int workerCount, Action action)
{
_wrkerCount = workerCount;
_action = action;
}
public void Start()
{
// Create and start a separate Task for each consumer:
for (int i = 0; i < _wrkerCount; i++)
{
Task.Factory.StartNew(_action);
}
}
}
So basically I will create OrderlyThreadPool for each subgroup.
I am recv messages from say source, which blocks if no message is available. So my code, looks like
while(true)
{
var message = GetMsg();
foreach(OrderlyThreadPool<Msg> a in myList)
{
a.AddMsg(message);
}
if(msgCount > 1000)
{
Wait for all threads to finish work;
}
else
{
msgCount =msgCount+1;
}
}
You start your tasks but you don't keep a reference. Simply store these tasks, expose them through the Worker and OrderlyThreadPool and use Task.WhenAll to wait for all of them to complete:
public class Worker
{
//...
List<Task> _tasks = new List<Task>();
public Task Completion { get { return Task.WhenAll(_tasks); } }
public void Start()
{
// Create and start a separate Task for each consumer:
for (int i = 0; i < _wrkerCount; i++)
{
Tasks.Add(Task.Factory.StartNew(_action));
}
}
}
public class OrderlyThreadPool<t> : IDisposable
{
//...
public Task Completion { get { return _worker.Completion; }}
}
await Task.WhenAll(myList.Select(orderlyThreadPool => orderlyThreadPool.Completion));
However, you should probably consider using TPL Dataflow instead. It's an actor-based framework that encapsulates completion, batching, concurrency levels and so forth...

Mutithreading with sequence

I have a main task that is spawning threads to do some work. When the work is completed it will write to the console.
My problem is that some of the threads that are created later will finish faster than those created earlier. However I need the writing to the console to be done in the same exact sequence as the thread was created.
So if a thread had completed its task, while some earlier threads had not, it has to wait till those earlier threads complete too.
public class DoRead
{
public DoRead()
{
}
private void StartReading()
{
int i = 1;
while (i < 10000)
{
Runner r = new Runner(i, "Work" + i.ToString());
r.StartThread();
i += 1;
}
}
}
internal class Runner : System.IDisposable
{
int _count;
string _work = "";
public Runner(int Count, string Work)
{
_count = Count;
_work = Work;
}
public void StartThread()
{
ThreadPool.QueueUserWorkItem(new WaitCallback(runThreadInPool), this);
}
public static void runThreadInPool(object obj)
{
((Runner)obj).run();
}
public void run()
{
try
{
Random r = new Random();
int num = r.Next(1000, 2000);
DateTime end = DateTime.Now.AddMilliseconds(num);
while (end > DateTime.Now)
{
}
Console.WriteLine(_count.ToString() + " : Done!");
}
catch
{
}
finally
{
_work = null;
}
}
public void Dispose()
{
this._work = null;
}
}
There may be a simpler way to do this than I used, (I'm used to .Net 4.0).
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace ConsoleApplication5
{
class Program
{
public static readonly int numOfTasks = 100;
public static int numTasksLeft = numOfTasks;
public static readonly object TaskDecrementLock = new object();
static void Main(string[] args)
{
DoRead dr = new DoRead();
dr.StartReading();
int tmpNumTasks = numTasksLeft;
while ( tmpNumTasks > 0 )
{
Thread.Sleep(1000);
tmpNumTasks = numTasksLeft;
}
List<string> strings = new List<string>();
lock( DoRead.locker )
{
for (int i = 1; i <= Program.numOfTasks; i++)
{
strings.Add( DoRead.dicto[i] );
}
}
foreach (string s in strings)
{
Console.WriteLine(s);
}
Console.ReadLine();
}
public class DoRead
{
public static readonly object locker = new object();
public static Dictionary<int, string> dicto = new Dictionary<int, string>();
public DoRead()
{
}
public void StartReading()
{
int i = 1;
while (i <= Program.numOfTasks )
{
Runner r = new Runner(i, "Work" + i.ToString());
r.StartThread();
i += 1;
}
}
}
internal class Runner : System.IDisposable
{
int _count;
string _work = "";
public Runner(int Count, string Work)
{
_count = Count;
_work = Work;
}
public void StartThread()
{
ThreadPool.QueueUserWorkItem(new WaitCallback(runThreadInPool), this);
}
public static void runThreadInPool(object obj)
{
Runner theRunner = ((Runner)obj);
string theString = theRunner.run();
lock (DoRead.locker)
{
DoRead.dicto.Add( theRunner._count, theString);
}
lock (Program.TaskDecrementLock)
{
Program.numTasksLeft--;
}
}
public string run()
{
try
{
Random r = new Random();
int num = r.Next(1000, 2000);
Thread.Sleep(num);
string theString = _count.ToString() + " : Done!";
return theString;
}
catch
{
}
finally
{
_work = null;
}
return "";
}
public void Dispose()
{
this._work = null;
}
}
}
}
Basically, I store the string you want printed from each task into a dictionary where the index is the task#. (I use a lock to make accessing the dictionary safe).
Next, so that the main program waits until all the background threads are done, I used another locked access to a NumTasksLeft variable.
I added stuff into the callback for the Runner.
It is bad practice to use busy loops, so I changed it to a Thread.Sleep( num ) statement.
Just change numOfTasks to 10000 to match your example.
I pull the return strings out of the dictionary in order, and then print it to the screen.
I'm sure you could refactor this to move or otherwise deal with the global variables, but this works.
Also, you might have noticed I didn't use the lock in the command
tmpNumTasks = numTasksLeft;
That's threadsafe, since numTasksLeft is an int which is read atomically on 32-bit computers and higher.
I don't know much on C#, but the whole idea of multi-threading is that you have multiple thread executing independently and you can never know which one will finish earlier (and you shouldn't expect earlier thread to end earlier).
One workaround is, instead writing out the finish message in the processing thread, have the processing thread setup a flag somewhere (probably a list with no of elements = no of thread spawned), and have a separate thread print out the finish message base on the flags in that list, and report up to the position that previous flag is consecutively "finished".
Honestly I don't feel that reasonable for you to print finish message like this anyway. I think changing the design is way better to have such meaningless "feature".
Typically, such requirements are met with an incrementing sequence number, much as you have already done.
Usually, the output from the processing threads is fed through a filter object that contains a list, (or dictionary), of all out-of-order result objects, 'holding them back' until all results with a lower seqeuence-number have come in. Again, similar to what you have already done.
What is not necessary is any kind of sleep() loop. The work threads themselves can operate the filter object, (which would beed a lock), or the work threads can producer-consumer-queue the results to an 'output thread' that operates the out-of-order filter.
This scheme works fine with pooled work threads, ie. those without continual create/terminate/destroy overhead.

Categories

Resources