What is wrong with this code to raise an ObjectDisposedException?

It’s a little difficult for me to understand the real behavior in this scenario. What actually happens in order not to complete the task when it is expected, but later, when SemaphoreSlim has been disposed of? Throws the following exception- System.ObjectDisposedException {"The semaphore has been disposed."}

I have a class library like -

 public class ParallelProcessor { private Action[] actions; private int maxConcurrency; public ParallelProcessor(Action[] actionList, int maxConcurrency) { this.actions = actionList; this.maxConcurrency = maxConcurrency; } public void RunAllActions() { if (Utility.IsNullOrEmpty<Action>(actions)) throw new Exception("No Action Found!"); using (SemaphoreSlim concurrencySemaphore = new SemaphoreSlim(maxConcurrency)) { foreach (Action action in actions) { Task.Factory.StartNew(() => { concurrencySemaphore.Wait(); try { action(); } finally { concurrencySemaphore.Release(); } }); } } } } 

And I use it like-

 class Program { static void Main(string[] args) { int maxConcurrency = 3; Action[] actions = new Action[] { () => Console.WriteLine(1), () => Console.WriteLine(2), () => Console.WriteLine(3) }; //Array.Empty<Action>(); ParallelProcessor processor = new ParallelProcessor(actions, maxConcurrency); processor.RunAllActions(); Console.ReadLine(); } } 

Can anyone shed some light on this? Thanks in advance.

+6
source share
3 answers

The problem is your using statement. Here's how it goes:

  • Creating a semaphore
  • Running tasks running in the background
  • Semaphore disposal
  • Tasks try to use a semaphore ... but they cannot, because it is located

Parameters:

  • Just remove the using statement (so you don’t get rid of the semaphore, but this is unlikely to be a problem if you don’t use it really hard)
  • Change your method to lock (inside the using statement) until all tasks are completed, for example. using Parallel.ForEach instead of directly calling Task.Factory.StartNew
  • Change your code to get rid of the semaphore in the task, which will be executed only after the completion of all other tasks.
+11
source

Your semaphore is located at the end of the using block, but is used by the still running Task created inside it.
I would recommend moving the semaphore to class level:

 public class ParallelProcessor { private Action[] actions; private SemaphoreSlim concurrencySemaphore; public ParallelProcessor(Action[] actionList, int maxConcurrency) { this.actions = actionList; concurrencySemaphore = new SemaphoreSlim(maxConcurrency); } public void RunAllActions() { if (Utility.IsNullOrEmpty<Action>(actions)) throw new Exception("No Action Found!"); foreach (Action action in actions) { Task.Factory.StartNew(() => { concurrencySemaphore.Wait(); try { action(); } finally { concurrencySemaphore.Release(); } }); } } } 

or an alternative approach where RunAllActions will block until all are executed:

 public class ParallelProcessor { private Action[] actions; private int maxConcurrency; public ParallelProcessor(Action[] actionList, int maxConcurrency) { this.actions = actionList; this.maxConcurrency = maxConcurrency; } public void RunAllActions() { if (Utility.IsNullOrEmpty<Action>(actions)) throw new Exception("No Action Found!"); using (var concurrencySemaphore = new SemaphoreSlim(maxConcurrency)) { Task.WaitAll(actions.Select(a => Task.Run(() => { concurrencySemaphore.Wait(); try { a(); } finally { concurrencySemaphore.Release(); } })).ToArray()); } } } 
+3
source

I believe the problem is with the concurrencySemaphore utility, which is already in the using statement.

The main use The use is that it will automatically add a try and, finally, and finally will destroy the object that is under , using .

https://www.codeproject.com/Articles/6564/Understanding-the-using-statement-in-C

The solution for your case either removes the use or deletion of the finally statement

0
source

Source: https://habr.com/ru/post/1013569/


All Articles