How do I close down and wait for a semaphore to be fully released?
private SemaphoreSlim _processSemaphore = new SemaphoreSlim(10);
public async Task<Modification> Process(IList<Command> commands)
{
Assert.IsFalse(_shuttingDown, "Server is in shutdown phase");
await _processSemaphore.WaitAsync();
try
{
// threads that have reached this far must be allowed to complete
return _database.Process(commands);
}
finally
{
_processSemaphore.Release();
}
}
public async Task StopAsync()
{
_shuttingDown = true;
// how wait for threads to complete without cancelling?
await ?
}
private SemaphoreSlim _processSemaphore = new SemaphoreSlim(10);
private int _concurrency;
private TaskCompletionSource<int> _source;
private ManualResetEvent _awaitor;
public void Start()
{
//solution 1
_concurrency = 0;
_source = new TaskCompletionSource<int>();
_shuttingDown = false;
//solution 2
_awaitor = new ManualResetEvent(false);
//your code
}
public async Task<Modification> Process(IList<Command> commands)
{
Interlocked.Increment(ref _concurrency);
Assert.IsFalse(_shuttingDown, "Server is in shutdown phase");
await _processSemaphore.WaitAsync();
try
{
// threads that have reached this far must be allowed to complete
return _database.Process(commands);
}
finally
{
_processSemaphore.Release();
//check and release
int concurrency = Interlocked.Decrement(ref _concurrency);
if (_shuttingDown && concurrency == 0)
{
//solution 1
_source.TrySetResult(0);
//solution 2
_awaitor.Set();
}
}
}
public async Task StopAsync()
{
_shuttingDown = true;
// how wait for threads to complete without cancelling?
if (Interlocked.CompareExchange(ref _concurrency, 0, 0) != 0)
{
await _source.Task;//solution 1
_awaitor.WaitOne();//solution 2
}
}
Might not be exactly what you need, but I had a similar case and I solved it with the CountdownEvent class
private CountdownEvent _countdownEvent = new CountdownEvent(1);
process_method() {
//if the count is zero means that we already called finalize
if (_countdownEvent.IsSet)
return;
try
{
//this can throw and exception if we try to add when the countdown has already reached 0.
//this exception happens when one process_method B has passed the _counddownEvent.IsSet check and context switched to
//to another process_method A that was the last one (after finalize waits for 0) and sets the countdown to 0. Which
//triggers finalization and should not allow new process_method, so process_methodB not continuing is good (finalization is
//in progress).
_countdownEvent.AddCount(1);
} catch
{
return;
}
try
{
//your process
}
finally
{
_countdownEvent.Signal();
}
}
And then when you are ready to wait for the count to be zero:
finalize() {
_countdownEvent.Signal();
_countdownEvent.Wait(_finalizationSafetyTimeout, cancellationToken)
}
Related
Following several tutorials on threaded app development, I've implemented a small practice utility that uses both Task.Run, and Threadpool.QueueUserWorkItem to recursively index a folder and generate a checksum manifest of all contained files.
While the application works well, I'm unable to get the UI to update during the portion that specifically uses a threadpool. The UI does update correctly using await Application.Current.Dispatcher.BeginInvoke when using Task.Run.
The desired behavior is for the progress to continue to update within ProcessChecksums as each of the threadpool's threads finish their individual task.
int queuedThreads = 0;
object locker = new object();
CancellationTokenSource cancellationTokenSource;
List<string> lstFilePaths = new List<string>();
Manifest manifestData;
event Action<double> OnProgressChange;
event Action<string> OnStatusChange;
async void GenerateManifest(Object sender, RoutedEventArgs e) {
status = "Indexing Files";
progress = 1;
cancellationTokenSource = new CancellationTokenSource();
await Task.Run(() => Async_GenerateManifest(cancellationTokenSource.Token), cancellationTokenSource.Token);
if (!cancellationTokenSource.Token.IsCancellationRequested) {
Finished();
}else{
Cancelled();
}
}
async Task Async_GenerateManifest(CancellationToken cancellationToken) {
if (cancellationToken.IsCancellationRequested) { return; }
Async_IndexFiles(initialpath, cancellationToken);
//Works Perfectly
await Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() => {
OnStatusChange("Generating Checksums");
OnProgressChange(10);
}));
ProcessChecksums(cancellationToken);
//Works Perfectly
await Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() => {
OnStatusChange("Saving Manifest");
OnProgressChange(90);
}));
SaveManifestFile(cancellationToken);
}
void ProcessChecksums(CancellationToken cancellationToken) {
List<FileData> lstFileData = new List<FileData>();
for (int i = 0; i < lstFilePaths.Count; i++) {
if (cancellationToken.IsCancellationRequested) { return; }
string s = lstFilePaths[i];
lock (locker) queuedThreads++;
ThreadPool.QueueUserWorkItem( x => {
manifestData.AddFileData(new FileData(s, GenerateChecksum(s)));
});
}
lock (locker) {
while (queuedThreads > 0) {
if (cancellationToken.IsCancellationRequested) { return; }=
Monitor.Wait(locker);
//Can't use await Dispatcher.BeginInvoke as is inside a lock, doesn't update GUI while waiting.
OnProgressChange((((queuedThreads - lstFilePaths.Count) * -1) / lstFilePaths.Count) - 0.2);
}
}
}
string GenerateChecksum(string filePath) {
//Time-consuming checksum generation
//...
lock (locker) {
queuedThreads--;
Monitor.Pulse(locker);
}
return BitConverter.ToString(checksum);
}
The standard pattern for updating the UI with progress updates from a background thread is to use IProgress<T>/Progress<T>. The more modern approach has several benefits over direct use of Dispatcher.
// You'd want to set these while on the UI thread.
// E.g., in your constructor:
// _percentProgress = new Progress<double>(value => ...);
// _statusProgress = new Progress<string>(value => ...);
IProgress<double> _percentProgress;
IProgress<string> _statusProgress;
async void GenerateManifest(Object sender, RoutedEventArgs e) {
status = "Indexing Files";
progress = 1;
cancellationTokenSource = new CancellationTokenSource();
await Task.Run(() => GenerateManifest(cancellationTokenSource.Token));
if (!cancellationTokenSource.Token.IsCancellationRequested) {
Finished();
}else{
Cancelled();
}
}
void GenerateManifest(CancellationToken cancellationToken) {
if (cancellationToken.IsCancellationRequested) { return; }
Async_IndexFiles(initialpath, cancellationToken);
_statusProgress?.Report("Generating Checksums");
_percentProgress?.Report(10);
ProcessChecksums(cancellationToken);
_statusProgress?.Report("Saving Manifest");
_percentProgress?.Report(90);
SaveManifestFile(cancellationToken);
}
void ProcessChecksums(CancellationToken cancellationToken) {
List<FileData> lstFileData = new List<FileData>();
for (int i = 0; i < lstFilePaths.Count; i++) {
if (cancellationToken.IsCancellationRequested) { return; }
string s = lstFilePaths[i];
lock (locker) queuedThreads++;
ThreadPool.QueueUserWorkItem( x => {
manifestData.AddFileData(new FileData(s, GenerateChecksum(s)));
});
}
lock (locker) {
while (queuedThreads > 0) {
if (cancellationToken.IsCancellationRequested) { return; }
Monitor.Wait(locker);
_percentProgress?.Report((((queuedThreads - lstFilePaths.Count) * -1) / lstFilePaths.Count) - 0.2);
}
}
}
There are a few other problems with the code, too. QueueUserWorkItem should probably be replaced with Task.Run and an await Task.WhenAll to ensure exceptions don't tear down the process. The if (...) Finished(); else Cancelled(); is probably best represented by changing the return type to Task. Use ThrowIfCancellationRequested instead of IsCancellationRequested. And there's a kind of Monitor.Wait without a Pulse that is just used for progress updates, which is odd; it would be cleaner to allow that code to report its own progress. If you take a look at each of these individually, you should find your code ends up cleaner.
Suppose I have a long operation inside a subroutine or function and I want to be able to cancel (exit subroutine or function) immediately after a "cancellation flag" is set to true. What is the best way to do it? One way is to check the flag after each line of code but that is not very elegant.
For example:
dim _CancelFlag as boolean = false
Sub LongOperation()
dim a as integer
a = 1
if _CancelFlag = True Then
Exit Sub
End If
a = 2
if _CancelFlag = True Then
Exit Sub
End If
'And so on...
End Sub
Of course a = 1 is only for illustration purpose. Say the operation is really long until a = 100 and it is not possible to put them into a loop, how can I trigger the cancellation from outside of the subroutine and stop it immediately?
I was thinking to put the sub into a BackgroundWorker or Task but then I still have to check for CancellationToken somewhere inside the sub.. Do I really have to check after each line of code?
It depends on the granularity you want to achieve: how many seconds can you expect your method be canceled?
If the cancellation must take place "immediately" you have to check in as many place as you can. However, just checking before and after long sub steps of your operation is enough in the general case.
Remember that if you have to wait on handles, you have to use the appropriate overload that specifies a timeout or a cancellation token.
Additionally, you should propagate the cancellation token/your flag deep down your methods to allow detection early the cancellation requests.
I found a more elegant way to do it, although it does use a loop in the end. Please let me know if anybody has an better solution. I will also update when I find something else.
Sub LongOperation()
dim state as integer = 0
Do while state < 100
Select Case state
Case 0
a = 1
Case 1
a = 2
Case Else
Exit do
End Select
If _CancelFlag = True Then
Exit Sub
End If
state += 1
Loop
End Sub
This is a sample windows application I have created to cancel or pause the log running task.
public partial class Form1 : Form
{
updateUI _updateGUI;
CancellationToken _cancelToken;
PauseTokenSource _pauseTokeSource;
public Form1()
{
InitializeComponent();
}
delegate void updateUI(dynamic value);
private void btnStartAsync_Click(object sender, EventArgs e)
{
_pauseTokeSource = new PauseTokenSource();
_cancelToken = default(CancellationToken);
_pauseTokeSource.onPause -= _pauseTokeSource_onPause;
_pauseTokeSource.onPause += _pauseTokeSource_onPause;
Task t = new Task(() => { LongRunning(_pauseTokeSource); }, _cancelToken);
t.Start();
}
private void _pauseTokeSource_onPause(object sender, PauseEventArgs e)
{
var message = string.Format("Task {0} at {1}", e.Paused ? "Paused" : "Resumed", DateTime.Now.ToString());
this.Invoke(_updateGUI, message);
}
private async void LongRunning(PauseTokenSource pause)
{
_updateGUI = new updateUI(SetUI);
for (int i = 0; i < 20; i++)
{
await pause.WaitWhilePausedAsync();
Thread.Sleep(500);
this.Invoke(_updateGUI, i.ToString() + " => " + txtInput.Text);
//txtOutput.AppendText(Environment.NewLine + i.ToString());
if (_cancelToken.IsCancellationRequested)
{
this.Invoke(_updateGUI, "Task cancellation requested at " + DateTime.Now.ToString());
break;
}
}
_updateGUI = null;
}
private void SetUI(dynamic output)
{
//txtOutput.AppendText(Environment.NewLine + count.ToString() + " => " + txtInput.Text);
txtOutput.AppendText(Environment.NewLine + output.ToString());
}
private void btnCancelTask_Click(object sender, EventArgs e)
{
_cancelToken = new CancellationToken(true);
}
private void btnPause_Click(object sender, EventArgs e)
{
_pauseTokeSource.IsPaused = !_pauseTokeSource.IsPaused;
btnPause.Text = _pauseTokeSource.IsPaused ? "Resume" : "Pause";
}
}
public class PauseTokenSource
{
public delegate void TaskPauseEventHandler(object sender, PauseEventArgs e);
public event TaskPauseEventHandler onPause;
private TaskCompletionSource<bool> _paused;
internal static readonly Task s_completedTask = Task.FromResult(true);
public bool IsPaused
{
get { return _paused != null; }
set
{
if (value)
{
Interlocked.CompareExchange(ref _paused, new TaskCompletionSource<bool>(), null);
}
else
{
while (true)
{
var tcs = _paused;
if (tcs == null) return;
if (Interlocked.CompareExchange(ref _paused, null, tcs) == tcs)
{
tcs.SetResult(true);
onPause?.Invoke(this, new PauseEventArgs(false));
break;
}
}
}
}
}
public PauseToken Token
{
get
{
return new PauseToken(this);
}
}
internal Task WaitWhilePausedAsync()
{
var cur = _paused;
if (cur != null)
{
onPause?.Invoke(this, new PauseEventArgs(true));
return cur.Task;
}
return s_completedTask;
}
}
public struct PauseToken
{
private readonly PauseTokenSource m_source;
internal PauseToken(PauseTokenSource source) { m_source = source; }
public bool IsPaused { get { return m_source != null && m_source.IsPaused; } }
public Task WaitWhilePausedAsync()
{
return IsPaused ?
m_source.WaitWhilePausedAsync() :
PauseTokenSource.s_completedTask;
}
}
public class PauseEventArgs : EventArgs
{
public PauseEventArgs(bool paused)
{
Paused = paused;
}
public bool Paused { get; private set; }
}
If your LongOperation() is well splittable into short operations (I assume a=1, a=2, ..., a=100 being all reasonably short) than you could wrap all the short operations into Tasks, put them into a TaskQueue and process that queue, checking between the Tasks if cancellation was requested.
If LongOperation() is difficult to split you could run the LongOperation() on a separate dedicated thread and abort that thrad on cancellation. Some have commented aborting a thread being dirty and not being recommended. Actually that's not that bad, if properly handled. Aborting a thread just raises a ThradAbortException within the thread method. So if there is a try - catch - finally in the LongOperation(), catching and handling the exception and if the finally code properly does cleanup, closes all handles, disposes etc., this should be ok and nothing to be afraid of.
I have found many methods of using the TaskFactory but I could not find anything about starting more tasks and watching when one ends and starting another one.
I always want to have 10 tasks working.
I want something like this
int nTotalTasks=10;
int nCurrentTask=0;
Task<bool>[] tasks=new Task<bool>[nThreadsNum];
for (int i=0; i<1000; i++)
{
string param1="test";
string param2="test";
if (nCurrentTask<10) // if there are less than 10 tasks then start another one
tasks[nCurrentThread++] = Task.Factory.StartNew<bool>(() =>
{
MyClass cls = new MyClass();
bool bRet = cls.Method1(param1, param2, i); // takes up to 2 minutes to finish
return bRet;
});
// How can I stop the for loop until a new task is finished and start a new one?
}
Check out the Task.WaitAny method:
Waits for any of the provided Task objects to complete execution.
Example from the documentation:
var t1 = Task.Factory.StartNew(() => DoOperation1());
var t2 = Task.Factory.StartNew(() => DoOperation2());
Task.WaitAny(t1, t2)
I would use a combination of Microsoft's Reactive Framework (NuGet "Rx-Main") and TPL for this. It becomes very simple.
Here's the code:
int nTotalTasks=10;
string param1="test";
string param2="test";
IDisposable subscription =
Observable
.Range(0, 1000)
.Select(i => Observable.FromAsync(() => Task.Factory.StartNew<bool>(() =>
{
MyClass cls = new MyClass();
bool bRet = cls.Method1(param1, param2, i); // takes up to 2 minutes to finish
return bRet;
})))
.Merge(nTotalTasks)
.ToArray()
.Subscribe((bool[] results) =>
{
/* Do something with the results. */
});
The key part here is the .Merge(nTotalTasks) which limits the number of concurrent tasks.
If you need to stop the processing part way thru just call subscription.Dispose() and everything gets cleaned up for you.
If you want to process each result as they are produced you can change the code from the .Merge(...) like this:
.Merge(nTotalTasks)
.Subscribe((bool result) =>
{
/* Do something with each result. */
});
This should be all you need, not complete, but all you need to do is wait on the first to complete and then run the second.
Task.WaitAny(task to wait on);
Task.Factory.StartNew()
Have you seen the BlockingCollection class? It allows you to have multiple threads running in parallel and you can wait from results from one task to execute another. See more information here.
The answer depends on whether the tasks to be scheduled are CPU or I/O bound.
For CPU-intensive work I would use Parallel.For() API setting the number of thread/tasks through MaxDegreeOfParallelism property of ParallelOptions
For I/O bound work the number of concurrently executing tasks can be significantly larger than the number of available CPUs, so the strategy is to rely on async methods as much as possible, which reduces the total number of threads waiting for completion.
How can I stop the for loop until a new task is finished and start a
new one?
The loop can be throttled by using await:
static void Main(string[] args)
{
var task = DoWorkAsync();
task.Wait();
// handle results
// task.Result;
Console.WriteLine("Done.");
}
async static Task<bool> DoWorkAsync()
{
const int NUMBER_OF_SLOTS = 10;
string param1="test";
string param2="test";
var results = new bool[NUMBER_OF_SLOTS];
AsyncWorkScheduler ws = new AsyncWorkScheduler(NUMBER_OF_SLOTS);
for (int i = 0; i < 1000; ++i)
{
await ws.ScheduleAsync((slotNumber) => DoWorkAsync(i, slotNumber, param1, param2, results));
}
ws.Complete();
await ws.Completion;
}
async static Task DoWorkAsync(int index, int slotNumber, string param1, string param2, bool[] results)
{
results[slotNumber] = results[slotNumber} && await Task.Factory.StartNew<bool>(() =>
{
MyClass cls = new MyClass();
bool bRet = cls.Method1(param1, param2, i); // takes up to 2 minutes to finish
return bRet;
}));
}
A helper class AsyncWorkScheduler uses TPL.DataFlow components as well as Task.WhenAll():
class AsyncWorkScheduler
{
public AsyncWorkScheduler(int numberOfSlots)
{
m_slots = new Task[numberOfSlots];
m_availableSlots = new BufferBlock<int>();
m_errors = new List<Exception>();
m_tcs = new TaskCompletionSource<bool>();
m_completionPending = 0;
// Initial state: all slots are available
for(int i = 0; i < m_slots.Length; ++i)
{
m_slots[i] = Task.FromResult(false);
m_availableSlots.Post(i);
}
}
public async Task ScheduleAsync(Func<int, Task> action)
{
if (Volatile.Read(ref m_completionPending) != 0)
{
throw new InvalidOperationException("Unable to schedule new items.");
}
// Acquire a slot
int slotNumber = await m_availableSlots.ReceiveAsync().ConfigureAwait(false);
// Schedule a new task for a given slot
var task = action(slotNumber);
// Store a continuation on the task to handle completion events
m_slots[slotNumber] = task.ContinueWith(t => HandleCompletedTask(t, slotNumber), TaskContinuationOptions.ExecuteSynchronously);
}
public async void Complete()
{
if (Interlocked.CompareExchange(ref m_completionPending, 1, 0) != 0)
{
return;
}
// Signal the queue's completion
m_availableSlots.Complete();
await Task.WhenAll(m_slots).ConfigureAwait(false);
// Set completion
if (m_errors.Count != 0)
{
m_tcs.TrySetException(m_errors);
}
else
{
m_tcs.TrySetResult(true);
}
}
public Task Completion
{
get
{
return m_tcs.Task;
}
}
void SetFailed(Exception error)
{
lock(m_errors)
{
m_errors.Add(error);
}
}
void HandleCompletedTask(Task task, int slotNumber)
{
if (task.IsFaulted || task.IsCanceled)
{
SetFailed(task.Exception);
return;
}
if (Volatile.Read(ref m_completionPending) == 1)
{
return;
}
// Release a slot
m_availableSlots.Post(slotNumber);
}
int m_completionPending;
List<Exception> m_errors;
BufferBlock<int> m_availableSlots;
TaskCompletionSource<bool> m_tcs;
Task[] m_slots;
}
So what i am trying to do here is:
Make the engine loop and work on an object if the queue is not empty.
If the queue is empty i call the manualresetevent to make the thread sleep.
When a item is added and the loop is not active i set the manualresetevent.
To make it faster i pick up atmost 5 items from the list and perform operation on them asynchronously and wait for all of them to finish.
Problem:
The clear methods on the two lists are called as soon as a new call to the AddToUpdateQueueMethod is called.
In my head as i am waiting for Task.WhenAll(tasks), so thread should wait for its completion before moving ahead, hence the clear on the lists should only be called on after Task.WhenAll(tasks) returns.
What am i missing here, or what will be a better way to achieve this.
public async Task ThumbnailUpdaterEngine()
{
int count;
List<Task<bool>> tasks = new List<Task<bool>>();
List<Content> candidateContents = new List<Content>();
while (true)
{
for (int i = 0; i < 5; i++)
{
Content nextContent = GetNextFromInternalQueue();
if (nextContent == null)
break;
else
candidateContents.Add(nextContent);
}
foreach (var candidateContent in candidateContents)
{
foreach (var provider in interactionProviders)
{
if (provider.IsServiceSupported(candidateContent.ServiceType))
{
Task<bool> task = provider.UpdateThumbnail(candidateContent);
tasks.Add(task);
break;
}
}
}
var results = await Task.WhenAll(tasks);
tasks.Clear();
foreach (var candidateContent in candidateContents)
{
if (candidateContent.ThumbnailLink != null && !candidateContent.ThumbnailLink.Equals(candidateContent.FileIconLink, StringComparison.CurrentCultureIgnoreCase))
{
Task<bool> task = DownloadAndUpdateThumbnailCache(candidateContent);
tasks.Add(task);
}
}
await Task.WhenAll(tasks);
//Clean up for next time the loop comes in.
tasks.Clear();
candidateContents.Clear();
lock (syncObject)
{
count = internalQueue.Count;
if (count == 0)
{
isQueueControllerRunning = false;
monitorEvent.Reset();
}
}
await Task.Run(() => monitorEvent.WaitOne());
}
}
private Content GetNextFromInternalQueue()
{
lock (syncObject)
{
Content nextContent = null;
if (internalQueue.Count > 0)
{
nextContent = internalQueue[0];
internalQueue.Remove(nextContent);
}
return nextContent;
}
}
public void AddToUpdateQueue(Content content)
{
lock (syncObject)
{
internalQueue.Add(content);
if (!isQueueControllerRunning)
{
isQueueControllerRunning = true;
monitorEvent.Set();
}
}
}
You should simply use TPL Dataflow. It's an actor framework on top of the TPL with an async support. Use an ActionBlock with an async action and MaxDegreeOfParallelism of 5:
var block = new ActionBlock<Content>(
async content =>
{
var tasks = interactionProviders.
Where(provider => provider.IsServiceSupported(content.ServiceType)).
Select(provider => provider.UpdateThumbnail(content));
await Task.WhenAll(tasks);
if (content.ThumbnailLink != null && !content.ThumbnailLink.Equals(
content.FileIconLink,
StringComparison.CurrentCultureIgnoreCase))
{
await DownloadAndUpdateThumbnailCache(content);
}
}, new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 5});
foreach (var content in GetContent())
{
block.Post(content);
}
block.Complete();
await block.Completion
I have tasks running that call a method that reads from RabbitMQ. When there is nothing in the queue, the method simply blocks. So the tasks have a "running" status, but aren't actually doing anything. Is there any way to gracefully end these tasks?
The code that accesses the queue is as follows:
private void FindWork(CancellationToken ct)
{
if (ct.IsCancellationRequested)
return;
bool result = false;
bool process = false;
bool queueResult = false;
Work_Work work = null;
try
{
using (Queue workQueue = new Queue(_workQueue))
{
// Look for work on the work queue
workQueue.Open(Queue.Mode.Consume);
work = workQueue.ConsumeWithBlocking<Work_Work>();
// Do some work with the message ...
return;
The tasks are created as follows:
private void Run()
{
while (!_stop)
{
// Remove and stopped tasks from the pool
List<int> removeThreads = new List<int>();
lock (_tasks)
{
foreach (KeyValuePair<int, Task> task in _tasks)
{
if (task.Value.Status != TaskStatus.Running)
{
task.Value.Wait();
removeThreads.Add(task.Value.Id);
}
}
foreach (int taskID in removeThreads)
_tasks.Remove(taskID);
}
CancellationToken ct = _cts.Token;
TaskFactory factory = new TaskFactory(ct, TaskCreationOptions.LongRunning, TaskContinuationOptions.LongRunning, null);
// Create new tasks if we have room in the pool
while (_tasks.Count < _runningMax)
{
Task task = factory.StartNew(() => FindWork(ct));
lock (_tasks)
_tasks.Add(task.Id, task);
}
// Take a rest so we don't run the CPU to death
Thread.Sleep(1000);
}
}
Currently I have changed my task creation code to look like the following so that I can abort the tasks. I know this is not a good solution, but I don't know what else to do.
while (_tasks.Count < _runningMax)
{
Task task = factory.StartNew(() =>
{
try
{
using (_cts.Token.Register(Thread.CurrentThread.Abort))
{
FindWork(ct);
}
}
catch (ThreadAbortException)
{
return;
}
}, _cts.Token);
_tasks.Add(task.Id, task);
}
Could the following work in your scenario?
Instead of spawning multiple threads and having them waiting in the queue, I would have a single thread in an infinite polling loop and having that one spawn a new thread when a new piede of work comes in. You can add a semaphore to limit the number of threads you create. Check sample code below, I've used a BlockingCollection instead of the RabbitMQ .
public class QueueManager
{
public BlockingCollection<Work> blockingCollection = new BlockingCollection<Work>();
private const int _maxRunningTasks = 3;
static SemaphoreSlim _sem = new SemaphoreSlim(_maxRunningTasks);
public void Queue()
{
blockingCollection.Add(new Work());
}
public void Consume()
{
while (true)
{
Work work = blockingCollection.Take();
_sem.Wait();
Task t = Task.Factory.StartNew(work.DoWork);
}
}
public class Work
{
public void DoWork()
{
Thread.Sleep(5000);
_sem.Release();
Console.WriteLine("Finished work");
}
}
}
and my testing class
class Test
{
static void Main(string[] args)
{
Consumer c = new Consumer();
Task t = Task.Factory.StartNew(c.Consume);
c.Queue();
c.Queue();
c.Queue();
c.Queue();
c.Queue();
Thread.Sleep(1000);
Console.ReadLine();
}
}
To make this work, you'd need to change ConsumeWithBlocking to support cancellation. I'm not familiar with RabbitMQ, but apparently it supports cancellation on the consumer channel.
So, instead of doing Thread.CurrentThread.Abort from Token.Register callback, do the right thing and cancel the operation gracefully via proper RabbitMQ API.
On a side note, the thread you're currently trying to abort is most likely not the one which is blocked by ConsumeWithBlocking.