Understanding Async/Await pattern for multi-tasking - c#

Currently, i am having difficulties to understand multi-tasking using async and await pattern. In order to get some basics, i have written the following test case;
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private int global_int = 10;
public async Task<int> RunAsyncTask()
{
// This method runs asynchronously.
await Task.Run(() => Calculate());
return global_int;
}
private int Calculate()
{
Console.WriteLine("Ticket count: " + --global_int);
return global_int;
}
private async void Start_Button_Click(object sender, RoutedEventArgs e)
{
List<Task<int>> list = new List<Task<int>>();
Console.WriteLine("\nReseting: " );
global_int = 10;
for (int i = 0; i < 10; i++)
{
var task = RunAsyncTask();
list.Add(task);
}
await Task.WhenAll(list.ToArray<Task<int>>());
Console.WriteLine("\nFinished: " + global_int);
}
}
Idea/Target:
10 customers, 10 tickets, every customer buys a ticket and at the end there will be no availiable ticket.
Problem:
When I run the code, i am actually getting not always the same result (Expecting 0 ticket always). Where is the actuall problem?
So, how can I write the code in a way that, result would be always same.
Output1:
Reseting:
Ticket count: 9
Ticket count: 8
Ticket count: 8
Ticket count: 7
Ticket count: 5
Ticket count: 6
Ticket count: 4
Ticket count: 3
Ticket count: 2
Ticket count: 1
Finished: 1
Output2:
Reseting:
Ticket count: 9
Ticket count: 8
Ticket count: 7
Ticket count: 6
Ticket count: 5
Ticket count: 4
Ticket count: 3
Ticket count: 2
Ticket count: 1
Ticket count: 0
Finished: 0

--global_int
This is not a thread-safe operation. Multiple threads are reading and writing to global_int, causing a race condition. There's a handy class called Interlocked to keep simple int operations atomic, change your Calculate method to:
Console.WriteLine("Ticket count: " + Interlocked.Decrement(ref global_int);

If you want to understand how tasks might be scheduled in a single-threaded fashion while still making use of async patterns, you may be interested in this code.
class Program
{
static void Main(string[] args)
{
InitiateCalculations().Wait();
Console.WriteLine("Finished: {0}", global_int);
}
// LimitedConcurrencyLevelTaskScheduler from
// https://msdn.microsoft.com/en-us/library/system.threading.tasks.taskscheduler
// Provides a task scheduler that ensures a maximum concurrency level while
// running on top of the thread pool.
public class LimitedConcurrencyLevelTaskScheduler : TaskScheduler
{
public static TaskFactory SingleFactory { get; private set; }
static LimitedConcurrencyLevelTaskScheduler()
{
SingleFactory = new TaskFactory(new LimitedConcurrencyLevelTaskScheduler(1));
}
// Indicates whether the current thread is processing work items.
[ThreadStatic]
private static bool _currentThreadIsProcessingItems;
// The list of tasks to be executed
private readonly LinkedList<Task> _tasks = new LinkedList<Task>(); // protected by lock(_tasks)
// The maximum concurrency level allowed by this scheduler.
private readonly int _maxDegreeOfParallelism;
// Indicates whether the scheduler is currently processing work items.
private int _delegatesQueuedOrRunning = 0;
// Creates a new instance with the specified degree of parallelism.
public LimitedConcurrencyLevelTaskScheduler(int maxDegreeOfParallelism)
{
if (maxDegreeOfParallelism < 1) throw new ArgumentOutOfRangeException("maxDegreeOfParallelism");
_maxDegreeOfParallelism = maxDegreeOfParallelism;
}
// Queues a task to the scheduler.
protected sealed override void QueueTask(Task task)
{
// Add the task to the list of tasks to be processed. If there aren't enough
// delegates currently queued or running to process tasks, schedule another.
lock (_tasks)
{
_tasks.AddLast(task);
if (_delegatesQueuedOrRunning < _maxDegreeOfParallelism)
{
++_delegatesQueuedOrRunning;
NotifyThreadPoolOfPendingWork();
}
}
}
// Inform the ThreadPool that there's work to be executed for this scheduler.
private void NotifyThreadPoolOfPendingWork()
{
ThreadPool.UnsafeQueueUserWorkItem(_ =>
{
// Note that the current thread is now processing work items.
// This is necessary to enable inlining of tasks into this thread.
_currentThreadIsProcessingItems = true;
try
{
// Process all available items in the queue.
while (true)
{
Task item;
lock (_tasks)
{
// When there are no more items to be processed,
// note that we're done processing, and get out.
if (_tasks.Count == 0)
{
--_delegatesQueuedOrRunning;
break;
}
// Get the next item from the queue
item = _tasks.First.Value;
_tasks.RemoveFirst();
}
// Execute the task we pulled out of the queue
base.TryExecuteTask(item);
}
}
// We're done processing items on the current thread
finally { _currentThreadIsProcessingItems = false; }
}, null);
}
// Attempts to execute the specified task on the current thread.
protected sealed override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{
// If this thread isn't already processing a task, we don't support inlining
if (!_currentThreadIsProcessingItems) return false;
// If the task was previously queued, remove it from the queue
if (taskWasPreviouslyQueued)
// Try to run the task.
if (TryDequeue(task))
return base.TryExecuteTask(task);
else
return false;
else
return base.TryExecuteTask(task);
}
// Attempt to remove a previously scheduled task from the scheduler.
protected sealed override bool TryDequeue(Task task)
{
lock (_tasks) return _tasks.Remove(task);
}
// Gets the maximum concurrency level supported by this scheduler.
public sealed override int MaximumConcurrencyLevel { get { return _maxDegreeOfParallelism; } }
// Gets an enumerable of the tasks currently scheduled on this scheduler.
protected sealed override IEnumerable<Task> GetScheduledTasks()
{
bool lockTaken = false;
try
{
Monitor.TryEnter(_tasks, ref lockTaken);
if (lockTaken) return _tasks;
else throw new NotSupportedException();
}
finally
{
if (lockTaken) Monitor.Exit(_tasks);
}
}
}
static private int global_int = 10;
public static Task<int> RunAsyncTask()
{
return LimitedConcurrencyLevelTaskScheduler.SingleFactory.StartNew<int>(Calculate);
}
private static int Calculate()
{
Thread.Sleep(500);
Console.WriteLine("Ticket count: {0} Thread: {1}", --global_int, Thread.CurrentThread.ManagedThreadId);
return global_int;
}
private static async Task InitiateCalculations()
{
List<Task<int>> list = new List<Task<int>>();
Console.WriteLine("\nReseting: ");
global_int = 10;
for (int i = 0; i < 10; i++)
{
var task = RunAsyncTask();
list.Add(task);
}
await Task.WhenAll(list.ToArray<Task<int>>());
}
}

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.

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

Task counter C#, Interlocked

I would like to run tasks in parallel, with no more than 10 instances running at a given time.
This is the code I have so far:
private void Listen()
{
while (true)
{
var context = listener.GetContext();
var task = Task.Run(() => HandleContextAsync(context));
Interlocked.Increment(ref countTask);
if (countTask > 10)
{
//I save tasks in the collection
}
else
{
task.ContinueWith(delegate { Interlocked.Decrement(ref countTask); }); //I accomplish the task and reduce the counter
}
}
}
I would suggest that you use a Parallel loop; for example:
Parallel.For(1, 10, a =>
{
var context = listener.GetContext();
...
});
That will start a defined number of tasks without you needing to manage the process yourself.
If you want to continually execute code in parallel, with up to 10 instances at a time, this may be worth considering:
private void Listen()
{
var options = new ParallelOptions() { MaxDegreeOfParallelism = 10 };
Parallel.For(1, long.MaxValue - 1, options, (i) =>
{
var context = listener.GetContext();
HandleContextAsync(context);
});
}
Basically, it will run the code continually (well roughly long.MaxValue times). MaxDegreeOfParallelism ensures that it runs only 10 'instances' of the code at a time.
I'm assuming that the result from GetContext is not created by you, so, its probably not useful to use a Parallel.For when you don't know how many times to run or don't have all the contexts to handle right away.
So, probably the best way to resolve this would be by implementing your own TaskScheduler. This way you can add more tasks to be resolved on demand with a fixed concurrency level.
Based on the example from Microsoft Docs website you can already achieve this.
I made an example program with some changes to the LimitedConcurrencyLevelTaskScheduler from the website.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace parallel
{
class Program
{
private static Random Rand = new Random();
static void Main(string[] args)
{
var ts = new LimitedConcurrencyLevelTaskScheduler(10);
var taskFactory = new TaskFactory(ts);
while (true)
{
var context = GetContext(ts);
if (context.Equals("Q", StringComparison.OrdinalIgnoreCase))
break;
taskFactory.StartNew(() => HandleContextAsync(context));
}
Console.WriteLine("Waiting...");
while (ts.CountRunning != 0)
{
Console.WriteLine("Now running {0}x tasks with {1}x queued.", ts.CountRunning, ts.CountQueued);
Thread.Yield();
Thread.Sleep(100);
}
}
private static void HandleContextAsync(string context)
{
// delays for 1-10 seconds to make the example easier to understand
Thread.Sleep(Rand.Next(1000, 10000));
Console.WriteLine("Context: {0}, from thread: {1}", context, Thread.CurrentThread.ManagedThreadId);
}
private static string GetContext(LimitedConcurrencyLevelTaskScheduler ts)
{
Console.WriteLine("Now running {0}x tasks with {1}x queued.", ts.CountRunning, ts.CountQueued);
return Console.ReadLine();
}
}
// Provides a task scheduler that ensures a maximum concurrency level while
// running on top of the thread pool.
public class LimitedConcurrencyLevelTaskScheduler : TaskScheduler
{
// Indicates whether the current thread is processing work items.
[ThreadStatic]
private static bool _currentThreadIsProcessingItems;
// The list of tasks to be executed
private readonly LinkedList<Task> _tasks = new LinkedList<Task>(); // protected by lock(_tasks)
public int CountRunning => _nowRunning;
public int CountQueued
{
get
{
lock (_tasks)
{
return _tasks.Count;
}
}
}
// The maximum concurrency level allowed by this scheduler.
private readonly int _maxDegreeOfParallelism;
// Indicates whether the scheduler is currently processing work items.
private volatile int _delegatesQueuedOrRunning = 0;
private volatile int _nowRunning;
// Creates a new instance with the specified degree of parallelism.
public LimitedConcurrencyLevelTaskScheduler(int maxDegreeOfParallelism)
{
if (maxDegreeOfParallelism < 1)
throw new ArgumentOutOfRangeException("maxDegreeOfParallelism");
_maxDegreeOfParallelism = maxDegreeOfParallelism;
}
// Queues a task to the scheduler.
protected sealed override void QueueTask(Task task)
{
// Add the task to the list of tasks to be processed. If there aren't enough
// delegates currently queued or running to process tasks, schedule another.
lock (_tasks)
{
_tasks.AddLast(task);
if (_delegatesQueuedOrRunning < _maxDegreeOfParallelism)
{
Interlocked.Increment(ref _delegatesQueuedOrRunning);
NotifyThreadPoolOfPendingWork();
}
}
}
// Inform the ThreadPool that there's work to be executed for this scheduler.
private void NotifyThreadPoolOfPendingWork()
{
ThreadPool.UnsafeQueueUserWorkItem(_ =>
{
// Note that the current thread is now processing work items.
// This is necessary to enable inlining of tasks into this thread.
_currentThreadIsProcessingItems = true;
try
{
// Process all available items in the queue.
while (true)
{
Task item;
lock (_tasks)
{
// When there are no more items to be processed,
// note that we're done processing, and get out.
if (_tasks.Count == 0)
{
Interlocked.Decrement(ref _delegatesQueuedOrRunning);
break;
}
// Get the next item from the queue
item = _tasks.First.Value;
_tasks.RemoveFirst();
}
// Execute the task we pulled out of the queue
Interlocked.Increment(ref _nowRunning);
if (base.TryExecuteTask(item))
Interlocked.Decrement(ref _nowRunning);
}
}
// We're done processing items on the current thread
finally { _currentThreadIsProcessingItems = false; }
}, null);
}
// Attempts to execute the specified task on the current thread.
protected sealed override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{
// If this thread isn't already processing a task, we don't support inlining
if (!_currentThreadIsProcessingItems) return false;
// If the task was previously queued, remove it from the queue
if (taskWasPreviouslyQueued)
// Try to run the task.
if (TryDequeue(task))
return base.TryExecuteTask(task);
else
return false;
else
return base.TryExecuteTask(task);
}
// Attempt to remove a previously scheduled task from the scheduler.
protected sealed override bool TryDequeue(Task task)
{
lock (_tasks) return _tasks.Remove(task);
}
// Gets the maximum concurrency level supported by this scheduler.
public sealed override int MaximumConcurrencyLevel { get { return _maxDegreeOfParallelism; } }
// Gets an enumerable of the tasks currently scheduled on this scheduler.
protected sealed override IEnumerable<Task> GetScheduledTasks()
{
bool lockTaken = false;
try
{
Monitor.TryEnter(_tasks, ref lockTaken);
if (lockTaken) return _tasks;
else throw new NotSupportedException();
}
finally
{
if (lockTaken) Monitor.Exit(_tasks);
}
}
}
}

Thread Join() causes Task.RunSynchronously not to finish

Calling _thread.Join() causes the GetConsumingEnumerable loop to be stuck on the last element. Why does this behavior occur?
public abstract class ActorBase : IDisposable
{
private readonly BlockingCollection<Task> _queue = new BlockingCollection<Task>(new ConcurrentQueue<Task>());
private readonly Thread _thread;
private bool _isDisposed;
protected ActorBase()
{
_thread = new Thread(ProcessMessages);
_thread.Start();
}
protected void QueueTask(Task task)
{
if (_isDisposed)
{
throw new Exception("Actor was disposed, cannot queue task.");
}
_queue.Add(task);
}
private void ProcessMessages()
{
foreach (var task in _queue.GetConsumingEnumerable())
{
task.RunSynchronously();
}
}
public void Dispose()
{
_isDisposed = true;
_queue.CompleteAdding();
_thread.Join();
}
}
public class SampleActor : ActorBase
{
private string GetThreadStatus()
{
Thread.Sleep(500);
return string.Format("Running on thread {0}", Thread.CurrentThread.ManagedThreadId);
}
public async Task<string> GetThreadStatusAsync()
{
var task = new Task<string>(GetThreadStatus);
QueueTask(task);
return await task;
}
}
class Program
{
public static async Task Run()
{
using (var sa = new SampleActor())
{
for (int i = 0; i < 3; i++)
{
Console.WriteLine(await sa.GetThreadStatusAsync());
}
}
}
public static void Main(string[] args)
{
Console.WriteLine("Main thread id {0}", Thread.CurrentThread.ManagedThreadId);
var task = Task.Run(async ()=> { await Run(); });
task.Wait();
}
}
The context for this approach is that I need to make sure that all operations are executed on one OS thread, which would allow a part of the app to use different credentials than the main thread.
async-await works with continuations. To be efficient and reduce scheduling these continuations usually run on the same thread that completed the previous task.
That means in your case that your special thread is not only running the tasks, it's also running all the continuations after these tasks (the for loop itself). You can see that by printing the thread id:
using (var sa = new SampleActor())
{
for (int i = 0; i < 3; i++)
{
Console.WriteLine(await sa.GetThreadStatusAsync());
Console.WriteLine("Continue on thread :" + Thread.CurrentThread.ManagedThreadId);
}
}
When the for loop completes and the SampleActor is being disposed you call Thread.Join from the same thread your are trying to join so you get a deadlock. Your situation boils down to this:
public static void Main()
{
Thread thread = null;
thread = new Thread(() =>
{
Thread.Sleep(100);
thread.Join();
Console.WriteLine("joined");
});
thread.Start();
}
In .Net 4.6 you can solve this with TaskCreationOptions.RunContinuationsAsynchronously but in the current version you can specify the default TaskScheduler:
public Task<string> GetThreadStatusAsync()
{
var task = new Task<string>(GetThreadStatus);
QueueTask(task);
return task.ContinueWith(task1 => task1.GetAwaiter().GetResult(), TaskScheduler.Default);
}
It might be tempting to put a simple check to see if the thread you're trying to Join is Thread.CurrentThread, but that would be wrong.
Furthermore, I think the whole approach - scheduling and running cold Task objects with a custom, non-TPL-compliant scheduler - is wrong. You should be using a TPL-friendly task scheduler, similar to Stephen Toub's StaTaskScheduler. Or run a custom SynchronizationContext for your actor-serving thread (like Toub's AsyncPump) and use TaskScheduler.FromCurrentSynchronizationContext and Task.Factory.StartNew to schedue tasks with your custom scheduler (or use Task.Start(TaskScheduler) if you have to deal with cold tasks).
This way, you'll have full control of where tasks and their continuations run, as well as of task inlining.

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...

Categories

Resources