Using Await Async WhenAll with Disposable Objects

I ran into a problem related to processing several tasks at the same time (or, nevertheless, runtime) when using disposable objects. In the following code fragment, each processor object is immediately deleted before it has completed the required work.

async public Task ProcessData(IEnumerable<int> data) { var tasks = new List<Task>(); foreach (var d in data) { using (var processor = new Processor(d)) { processor.Completed += (sender, e) => { // Do something else }; tasks.Add(processor.ProcessAsync()); } } await Task.WhenAll(tasks); } 

Re-writing the code, as shown below, leads to the fact that each Processor performs its processing and THEN is located, but this is not the most efficient way to run several Jobs that are not dependent on each other.

 async public Task ProcessData(IEnumerable<int> data) { foreach (var d in data) { using (var processor = new Processor(d)) { processor.Completed += (sender, e) => { // Do something else }; await processor.ProcessAsync(); } } } 

Can someone explain why the first example removes the β€œearly” one and gives an example of the best code for this situation.

+4
source share
2 answers

This helps to think of await as a suspension of the current method, although it does not block the thread.

In the first example, when you execute the foreach , every time you create a Processor , start the operation (keeping the Task operation in the list) and then delete the Processor . After the foreach completes, you (asynchronously) wait for all operations to complete.

In the second example, when you execute the foreach , every time you create a Processor , start the operation (asynchronously), wait for it to complete, and then delete the Processor .

To fix this, you should write a helper method, as such:

 private static async Task ProcessData(int data) { using (var processor = new Processor(d)) { processor.Completed += (sender, e) => { /* Do something else */ }; await processor.ProcessAsync(); } } 

Your helper method defines a higher-level operation that takes care of disposing of Processor own resource at the appropriate time. Then you can simultaneously start all your work as follows:

 public async Task ProcessData(IEnumerable<int> data) { ... await Task.WhenAll(data.Select(d => ProcessData(d))); ... } 
+7
source

Although Steven Cleary's answer is elegant and probably better in this case, I thought it worth paying attention to what you get as part of Reactive Extensions (Rx), which can be useful in such scenarios. It provides a bunch of IDisposable helpers to let you do this:

 public async Task ProcessData(IEnumerable<int> data) { var tasks = new List<Task>(); using (var disp = new CompositeDisposable()) { foreach (var d in data) { var processor = new Processor(d); disp.Add(processor); processor.Completed += (sender, e) => { // Do something else }; tasks.Add(processor.ProcessAsync()); } await Task.WhenAll(tasks); } } 

To get CompositeDisposable , you'll need a NuGet link to Rx-Main .

I don’t think that I will use this in this case, but then again I think that I will try not to get to the place where I need a code that looks like this. Having both a Task to represent the work performed by your Processor and an event to signal ... something? .. that it is completed ..? Well, Task can do this for you, so why so? And the events are relatively messy - I always find that either Rx IObservable<T> , or just using Task directly is a better solution than relying on events.

+2
source

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


All Articles